1use std::collections::HashMap;
2
3#[derive(Clone)]
4pub struct Args {
5 pub flags: HashMap<String, String>,
6 pub positional: Vec<String>,
7 pub verbosity: u8,
8 pub quiet: u8,
9}
10
11impl Args {
12 pub fn parse() -> Self {
13 Self::parse_from(std::env::args().skip(1).collect())
14 }
15
16 pub fn parse_from(args: Vec<String>) -> Self {
17 let mut flags = HashMap::new();
18 let mut positional = Vec::new();
19 let mut verbosity: u8 = 0;
20 let mut quiet: u8 = 0;
21 let mut iter = args.into_iter();
22
23 while let Some(arg) = iter.next() {
24 if arg == "--" {
25 positional.extend(iter);
26 break;
27 } else if let Some(key) = arg.strip_prefix("--") {
28 if let Some(eq_pos) = key.find('=') {
29 let (k, v) = key.split_at(eq_pos);
30 flags.insert(k.to_string(), v[1..].to_string());
31 continue;
32 }
33 match key {
34 "help" | "version" | "dry-run" | "disable-auth" | "no-http" => {
35 flags.insert(key.to_string(), "true".into());
36 }
37 _ => {
38 if let Some(value) = iter.next() {
39 flags.insert(key.to_string(), value);
40 } else {
41 flags.insert(key.to_string(), "true".into());
42 }
43 }
44 }
45 } else if arg.starts_with('-') && arg.len() > 1 {
46 let chars: Vec<char> = arg[1..].chars().collect();
47 for &c in &chars {
48 match c {
49 'v' => verbosity = verbosity.saturating_add(1),
50 'q' => quiet = quiet.saturating_add(1),
51 'h' => {
52 flags.insert("help".into(), "true".into());
53 }
54 'c' => {
55 if chars.len() == 1 {
56 if let Some(value) = iter.next() {
57 flags.insert("config".into(), value);
58 } else {
59 flags.insert("config".into(), "true".into());
60 }
61 } else {
62 flags.insert("config".into(), "true".into());
63 }
64 }
65 _ => {
66 flags.insert(c.to_string(), "true".into());
67 }
68 }
69 }
70 } else {
71 positional.push(arg);
72 }
73 }
74
75 Self {
76 flags,
77 positional,
78 verbosity,
79 quiet,
80 }
81 }
82
83 pub fn get(&self, key: &str) -> Option<&str> {
84 self.flags.get(key).map(|s| s.as_str())
85 }
86
87 pub fn has(&self, key: &str) -> bool {
88 self.flags.contains_key(key)
89 }
90
91 pub fn config_path(&self) -> Option<&str> {
92 self.get("config")
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::Args;
99
100 #[test]
101 fn parse_start_with_config() {
102 let args = Args::parse_from(vec![
103 "start".into(),
104 "--config".into(),
105 "/tmp/rns".into(),
106 "-vv".into(),
107 ]);
108 assert_eq!(args.positional, vec!["start"]);
109 assert_eq!(args.config_path(), Some("/tmp/rns"));
110 assert_eq!(args.verbosity, 2);
111 }
112
113 #[test]
114 fn parse_short_config() {
115 let args = Args::parse_from(vec!["start".into(), "-c".into(), "/tmp/rns".into()]);
116 assert_eq!(args.config_path(), Some("/tmp/rns"));
117 }
118}