| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384 |
- #![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)
- }
- /// Splits a string on its `=` character, returning the two substrings on
- /// either side. Returns `None` if there’s no equals or a string is missing.
- fn split_on_equals(input: &OsStr) -> Option<(&OsStr, &OsStr)> {
- use std::os::unix::ffi::OsStrExt;
- if let Some(index) = input.as_bytes().iter().position(|elem| *elem == b'=') {
- let (before, after) = input.as_bytes().split_at(index);
- // The after string contains the = that we need to remove.
- if before.len() >= 1 && after.len() >= 2 {
- return Some((OsStr::from_bytes(before),
- OsStr::from_bytes(&after[1..])))
- }
- }
- None
- }
- /// Creates an `OSString` (used in tests)
- #[cfg(test)]
- fn os(input: &'static str) -> OsString {
- let mut os = OsString::new();
- os.push(input);
- os
- }
- #[cfg(test)]
- mod split_test {
- use super::{split_on_equals, os};
- macro_rules! test_split {
- ($name:ident: $input:expr => None) => {
- #[test]
- fn $name() {
- assert_eq!(split_on_equals(&os($input)),
- None);
- }
- };
- ($name:ident: $input:expr => $before:expr, $after:expr) => {
- #[test]
- fn $name() {
- assert_eq!(split_on_equals(&os($input)),
- Some((&*os($before), &*os($after))));
- }
- };
- }
- test_split!(empty: "" => None);
- test_split!(letter: "a" => None);
- test_split!(just: "=" => None);
- test_split!(intro: "=bbb" => None);
- test_split!(denou: "aaa=" => None);
- test_split!(equals: "aaa=bbb" => "aaa", "bbb");
- test_split!(sort: "--sort=size" => "--sort", "size");
- test_split!(more: "this=that=other" => "this", "that=other");
- }
- #[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 }
- ];
- #[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())) ] }))
- }
- }
|