|
|
@@ -0,0 +1,341 @@
|
|
|
+#![allow(unused_variables, dead_code)]
|
|
|
+
|
|
|
+use std::ffi::{OsStr, OsString};
|
|
|
+
|
|
|
+
|
|
|
+pub type ShortArg = u8;
|
|
|
+pub type LongArg = &'static str;
|
|
|
+
|
|
|
+
|
|
|
+#[derive(PartialEq, Debug)]
|
|
|
+pub enum Flag {
|
|
|
+ Short(ShortArg),
|
|
|
+ Long(LongArg),
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(PartialEq, Debug)]
|
|
|
+pub enum Strictness {
|
|
|
+ ComplainAboutRedundantArguments,
|
|
|
+ UseLastArguments,
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Copy, Clone, PartialEq, Debug)]
|
|
|
+pub enum TakesValue {
|
|
|
+ Necessary,
|
|
|
+ Forbidden,
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(PartialEq, Debug)]
|
|
|
+pub struct Arg {
|
|
|
+ short: Option<ShortArg>,
|
|
|
+ long: LongArg,
|
|
|
+ takes_value: TakesValue,
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(PartialEq, Debug)]
|
|
|
+pub struct Args(&'static [Arg]);
|
|
|
+
|
|
|
+impl Args {
|
|
|
+ fn lookup_short(&self, short: ShortArg) -> Option<&Arg> {
|
|
|
+ self.0.into_iter().find(|arg| arg.short == Some(short))
|
|
|
+ }
|
|
|
+
|
|
|
+ fn lookup_long(&self, long: &OsStr) -> Option<&Arg> {
|
|
|
+ self.0.into_iter().find(|arg| arg.long == long)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#[derive(PartialEq, Debug)]
|
|
|
+pub struct Matches<'a> {
|
|
|
+ /// Long and short arguments need to be kept in the same vector, because
|
|
|
+ /// we usually want the one nearest the end to count.
|
|
|
+ flags: Vec<(Flag, Option<&'a OsStr>)>,
|
|
|
+ frees: Vec<&'a OsStr>,
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(PartialEq, Debug)]
|
|
|
+pub enum ParseError<'a> {
|
|
|
+ NeedsValue { flag: Flag },
|
|
|
+ ForbiddenValue { flag: Flag },
|
|
|
+ UnknownShortArgument { attempt: ShortArg },
|
|
|
+ UnknownArgument { attempt: &'a OsStr },
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+fn parse<'a>(args: Args, inputs: &'a [OsString]) -> Result<Matches<'a>, ParseError<'a>> {
|
|
|
+ use std::os::unix::ffi::OsStrExt;
|
|
|
+ use self::TakesValue::*;
|
|
|
+
|
|
|
+ let mut parsing = true;
|
|
|
+
|
|
|
+ let mut results = Matches {
|
|
|
+ flags: Vec::new(),
|
|
|
+ frees: Vec::new(),
|
|
|
+ };
|
|
|
+
|
|
|
+ let mut iter = inputs.iter();
|
|
|
+
|
|
|
+ while let Some(arg) = iter.next() {
|
|
|
+ let bytes = arg.as_bytes();
|
|
|
+
|
|
|
+ if !parsing {
|
|
|
+ results.frees.push(arg)
|
|
|
+ }
|
|
|
+ else if arg == "--" {
|
|
|
+ parsing = false;
|
|
|
+ }
|
|
|
+ else if bytes.starts_with(b"--") {
|
|
|
+ let long_arg = OsStr::from_bytes(&bytes[2..]);
|
|
|
+
|
|
|
+ if let Some((before, after)) = split_on_equals(long_arg) {
|
|
|
+ if let Some(&Arg { short: _, long: long_arg_name, takes_value }) = args.lookup_long(before) {
|
|
|
+ let flag = Flag::Long(long_arg_name);
|
|
|
+ match takes_value {
|
|
|
+ Necessary => results.flags.push((flag, Some(after))),
|
|
|
+ Forbidden => return Err(ParseError::ForbiddenValue { flag })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return Err(ParseError::UnknownArgument { attempt: before })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ if let Some(&Arg { short: _, long: long_arg_name, takes_value }) = args.lookup_long(long_arg) {
|
|
|
+ let flag = Flag::Long(long_arg_name);
|
|
|
+ match takes_value {
|
|
|
+ Forbidden => results.flags.push((flag, None)),
|
|
|
+ Necessary => {
|
|
|
+ if let Some(next_arg) = iter.next() {
|
|
|
+ results.flags.push((flag, Some(next_arg)));
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return Err(ParseError::NeedsValue { flag })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return Err(ParseError::UnknownArgument { attempt: long_arg })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if bytes.starts_with(b"-") && arg != "-" {
|
|
|
+ let short_arg = OsStr::from_bytes(&bytes[1..]);
|
|
|
+ if let Some((before, after)) = split_on_equals(short_arg) {
|
|
|
+ // TODO: remember to deal with the other bytes!
|
|
|
+ if let Some(&Arg { short, long, takes_value }) = args.lookup_short(*before.as_bytes().last().unwrap()) {
|
|
|
+ let flag = Flag::Short(short.unwrap());
|
|
|
+ match takes_value {
|
|
|
+ Necessary => results.flags.push((flag, Some(after))),
|
|
|
+ Forbidden => return Err(ParseError::ForbiddenValue { flag })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return Err(ParseError::UnknownArgument { attempt: before })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ for byte in &bytes[1..] {
|
|
|
+ // TODO: gotta check that these don't take arguments
|
|
|
+ // like -c4
|
|
|
+ if let Some(&Arg { short, long, takes_value }) = args.lookup_short(*byte) {
|
|
|
+ let flag = Flag::Short(*byte);
|
|
|
+ match takes_value {
|
|
|
+ Forbidden => results.flags.push((flag, None)),
|
|
|
+ Necessary => {
|
|
|
+ if let Some(next_arg) = iter.next() {
|
|
|
+ results.flags.push((flag, Some(next_arg)));
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return Err(ParseError::NeedsValue { flag })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return Err(ParseError::UnknownShortArgument { attempt: *byte });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ results.frees.push(arg)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(results)
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+fn split_on_equals<'a>(input: &OsStr) -> Option<(&OsStr, &OsStr)> {
|
|
|
+ use std::os::unix::ffi::OsStrExt;
|
|
|
+
|
|
|
+ input.as_bytes()
|
|
|
+ .iter()
|
|
|
+ .position(|elem| *elem == b'=')
|
|
|
+ .map(|index| input.as_bytes().split_at(index))
|
|
|
+ .map(|(b,a)| (OsStr::from_bytes(b), OsStr::from_bytes(&a[1..])))
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+#[cfg(test)]
|
|
|
+mod test {
|
|
|
+ use super::*;
|
|
|
+ use std::ffi::OsString;
|
|
|
+
|
|
|
+ static TEST_ARGS: &'static [Arg] = &[
|
|
|
+ Arg { short: Some(b'l'), long: "long", takes_value: TakesValue::Forbidden },
|
|
|
+ Arg { short: Some(b'c'), long: "count", takes_value: TakesValue::Necessary }
|
|
|
+ ];
|
|
|
+
|
|
|
+ fn os(input: &'static str) -> OsString {
|
|
|
+ let mut os = OsString::new();
|
|
|
+ os.push(input);
|
|
|
+ os
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn empty() {
|
|
|
+ let bits = [ ];
|
|
|
+ let results = parse(Args(TEST_ARGS), &bits);
|
|
|
+ assert_eq!(results, Ok(Matches { frees: vec![], flags: vec![] }))
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn filename() {
|
|
|
+ let bits = [ os("exa") ];
|
|
|
+ let results = parse(Args(TEST_ARGS), &bits);
|
|
|
+ assert_eq!(results, Ok(Matches { frees: vec![ os("exa").as_os_str() ], flags: vec![] }))
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn the_dashes_do_nothing() {
|
|
|
+ let bits = [ os("--") ];
|
|
|
+ let results = parse(Args(TEST_ARGS), &bits);
|
|
|
+ assert_eq!(results, Ok(Matches { frees: vec![], flags: vec![] }))
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn but_just_one_does() {
|
|
|
+ let bits = [ os("-") ];
|
|
|
+ let results = parse(Args(TEST_ARGS), &bits);
|
|
|
+ assert_eq!(results, Ok(Matches { frees: vec![ os("-").as_os_str() ], flags: vec![] }))
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ // ----- long args --------
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn as_filename() {
|
|
|
+ let bits = [ os("--"), os("--long") ];
|
|
|
+ let results = parse(Args(TEST_ARGS), &bits);
|
|
|
+ assert_eq!(results, Ok(Matches { frees: vec![os("--long").as_os_str() ], flags: vec![] }))
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn long() {
|
|
|
+ let bits = [ os("--long") ];
|
|
|
+ let results = parse(Args(TEST_ARGS), &bits);
|
|
|
+ assert_eq!(results, Ok(Matches { frees: vec![], flags: vec![ (Flag::Long("long"), None) ] }))
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn long_equals() {
|
|
|
+ let bits = [ os("--long=equals") ];
|
|
|
+ let results = parse(Args(TEST_ARGS), &bits);
|
|
|
+ assert_eq!(results, Err(ParseError::ForbiddenValue { flag: Flag::Long("long") }))
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn no_arg_separate() {
|
|
|
+ let bits = [ os("--long"), os("4") ];
|
|
|
+ let results = parse(Args(TEST_ARGS), &bits);
|
|
|
+ assert_eq!(results, Ok(Matches { frees: vec![ os("4").as_os_str() ], flags: vec![ (Flag::Long("long"), None) ] }))
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn no_arg_given() {
|
|
|
+ let bits = [ os("--count") ];
|
|
|
+ let results = parse(Args(TEST_ARGS), &bits);
|
|
|
+ assert_eq!(results, Err(ParseError::NeedsValue { flag: Flag::Long("count") }))
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn arg_equals() {
|
|
|
+ let bits = [ os("--count=4") ];
|
|
|
+ let results = parse(Args(TEST_ARGS), &bits);
|
|
|
+ assert_eq!(results, Ok(Matches { frees: vec![], flags: vec![ (Flag::Long("count"), Some(os("4").as_os_str())) ] }))
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn arg_separate() {
|
|
|
+ let bits = [ os("--count"), os("4") ];
|
|
|
+ let results = parse(Args(TEST_ARGS), &bits);
|
|
|
+ assert_eq!(results, Ok(Matches { frees: vec![], flags: vec![ (Flag::Long("count"), Some(os("4").as_os_str())) ] }))
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ // ----- short args --------
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn short_as_filename() {
|
|
|
+ let bits = [ os("--"), os("-l") ];
|
|
|
+ let results = parse(Args(TEST_ARGS), &bits);
|
|
|
+ assert_eq!(results, Ok(Matches { frees: vec![os("-l").as_os_str() ], flags: vec![] }))
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn short_long() {
|
|
|
+ let bits = [ os("-l") ];
|
|
|
+ let results = parse(Args(TEST_ARGS), &bits);
|
|
|
+ assert_eq!(results, Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'l'), None) ] }))
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn short_long_equals() {
|
|
|
+ let bits = [ os("-l=equals") ];
|
|
|
+ let results = parse(Args(TEST_ARGS), &bits);
|
|
|
+ assert_eq!(results, Err(ParseError::ForbiddenValue { flag: Flag::Short(b'l') }))
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn short_no_arg_separate() {
|
|
|
+ let bits = [ os("-l"), os("4") ];
|
|
|
+ let results = parse(Args(TEST_ARGS), &bits);
|
|
|
+ assert_eq!(results, Ok(Matches { frees: vec![ os("4").as_os_str() ], flags: vec![ (Flag::Short(b'l'), None) ] }))
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn short_no_arg_given() {
|
|
|
+ let bits = [ os("-c") ];
|
|
|
+ let results = parse(Args(TEST_ARGS), &bits);
|
|
|
+ assert_eq!(results, Err(ParseError::NeedsValue { flag: Flag::Short(b'c') }))
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn short_arg_equals() {
|
|
|
+ let bits = [ os("-c=4") ];
|
|
|
+ let results = parse(Args(TEST_ARGS), &bits);
|
|
|
+ assert_eq!(results, Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'c'), Some(os("4").as_os_str())) ] }))
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn short_arg_separate() {
|
|
|
+ let bits = [ os("-c"), os("4") ];
|
|
|
+ let results = parse(Args(TEST_ARGS), &bits);
|
|
|
+ assert_eq!(results, Ok(Matches { frees: vec![], flags: vec![ (Flag::Short(b'c'), Some(os("4").as_os_str())) ] }))
|
|
|
+ }
|
|
|
+
|
|
|
+}
|