Skip to main content

rns_server/
args.rs

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}