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 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}