minicmd/
lib.rs

1use std::{
2    collections::{HashMap, HashSet},
3    env,
4    hash::Hash,
5};
6
7#[derive(Debug, Clone, PartialEq)]
8pub struct MiniCmd {
9    arguments: Vec<String>,
10    flags: HashSet<String>,
11    values: HashMap<String, String>,
12}
13
14impl Hash for MiniCmd {
15    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
16        self.arguments.hash(state);
17
18        let mut flags = self.flags.iter().collect::<Vec<_>>();
19        flags.sort();
20        flags.hash(state);
21
22        let mut values = self.values.iter().collect::<Vec<_>>();
23        values.sort_by_cached_key(|x| x.0);
24        values.hash(state);
25    }
26}
27
28impl MiniCmd {
29    pub fn parse() -> Self {
30        Self::parse_from(env::args().skip(1))
31    }
32
33    pub fn parse_from<I, T>(args: I) -> Self
34    where
35        I: IntoIterator<Item = T>,
36        T: Into<String> + Clone,
37    {
38        let mut arguments = Vec::new();
39        let mut flags = HashSet::default();
40        let mut values = HashMap::default();
41
42        for arg in args
43            .into_iter()
44            .map(|x| std::convert::Into::<String>::into(x))
45        {
46            if let Some(arg) = arg.strip_prefix("-") {
47                if let Some((name, value)) = arg.split_once('=') {
48                    values.insert(name.to_owned(), value.to_owned());
49                } else {
50                    flags.insert(arg.to_owned());
51                }
52            } else {
53                arguments.push(arg);
54            }
55        }
56
57        Self {
58            arguments,
59            flags,
60            values,
61        }
62    }
63
64    /// Returns an iterator of arguments
65    pub fn args(&self) -> impl Iterator<Item = &str> {
66        self.arguments.iter().map(String::as_str)
67    }
68
69    pub fn args_len(&self) -> usize {
70        self.arguments.len()
71    }
72
73    pub fn args_is_empty(&self) -> bool {
74        self.args_len() == 0
75    }
76
77    pub fn flag(&self, name: &str) -> bool {
78        self.flags.contains(name)
79    }
80
81    pub fn flags(&self) -> impl Iterator<Item = &str> {
82        self.flags.iter().map(String::as_str)
83    }
84
85    pub fn value(&self, name: &str) -> Option<&str> {
86        self.values.get(name).map(String::as_str)
87    }
88
89    pub fn values(&self) -> impl Iterator<Item = (&str, &str)> {
90        self.values.iter().map(|(k, v)| (k.as_str(), v.as_str()))
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use std::hash::{DefaultHasher, Hasher};
97
98    use super::*;
99
100    #[test]
101    fn args_parsing() {
102        let config = MiniCmd::parse_from(vec!["abc", "def", "ghi"]);
103
104        let mut itr = config.args();
105
106        assert_eq!(itr.next().unwrap(), "abc");
107        assert_eq!(itr.next().unwrap(), "def");
108        assert_eq!(itr.next().unwrap(), "ghi");
109        assert_eq!(itr.next(), None);
110    }
111
112    #[test]
113    fn flag_parsing() {
114        let config = MiniCmd::parse_from(vec!["-a", "-b", "argument-in-here", "-abcdef"]);
115
116        assert!(config.flag("a"));
117        assert!(config.flag("b"));
118        assert!(config.flag("abcdef"));
119        assert_eq!(config.args().next().unwrap(), "argument-in-here");
120    }
121
122    #[test]
123    fn value_parsing() {
124        let config = MiniCmd::parse_from(vec!["-a=value", "-key=another-value", "other-arg"]);
125
126        assert_eq!(config.args().next().unwrap(), "other-arg");
127
128        assert_eq!(config.value("a").unwrap(), "value");
129        assert_eq!(config.value("key").unwrap(), "another-value");
130    }
131
132    #[test]
133    fn hashing_config() {
134        let config1 = MiniCmd::parse_from(vec!["-a", "thing", "-b", "-c", "-hash=yes"]);
135        let config2 = MiniCmd::parse_from(vec!["-b", "-a", "-hash=yes", "-c", "thing"]);
136
137        let mut hasher1 = DefaultHasher::new();
138        config1.hash(&mut hasher1);
139        let hash1 = hasher1.finish();
140
141        let mut hasher2 = DefaultHasher::new();
142        config2.hash(&mut hasher2);
143        let hash2 = hasher2.finish();
144
145        assert_eq!(hash1, hash2)
146    }
147}