1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
use clap::{
    error::{ContextKind, ContextValue, ErrorKind},
    Arg, Command, Error,
};
use std::ffi::OsStr;

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct EnvVarArg {
    pub key: String,
    pub value: String,
}

impl EnvVarArg {
    pub fn parse(input: &str) -> Option<Self> {
        input.split_once('=').map(|(k, v)| Self {
            key: k.to_string(),
            value: v.to_string(),
        })
    }
}

#[derive(Debug, Clone)]
pub struct EnvVarArgParser;

impl clap::builder::TypedValueParser for EnvVarArgParser {
    type Value = EnvVarArg;

    fn parse_ref(
        &self,
        cmd: &Command,
        arg: Option<&Arg>,
        value: &OsStr,
    ) -> Result<Self::Value, Error> {
        if let Some(parsed) = EnvVarArg::parse(&value.to_string_lossy()) {
            return Ok(parsed);
        }

        let mut err = clap::Error::new(ErrorKind::ValueValidation).with_cmd(cmd);
        if let Some(arg) = arg {
            err.insert(
                ContextKind::InvalidArg,
                ContextValue::String(arg.to_string()),
            );
        }
        err.insert(
            ContextKind::InvalidValue,
            ContextValue::String(value.to_string_lossy().into()),
        );
        Err(err)
    }
}

#[cfg(test)]
mod tests {
    use super::EnvVarArg;

    #[test]
    fn invalid_value() {
        let res = EnvVarArg::parse("NO_EQUAL_SIGN");
        assert!(res.is_none());
    }

    #[test]
    fn valid_values() {
        let values = [
            ("FOO=", new_arg("FOO", "")),
            ("FOO=bar", new_arg("FOO", "bar")),
        ];

        for (input, want) in values {
            let got = EnvVarArg::parse(input);
            assert_eq!(got, Some(want));
        }
    }

    fn new_arg(key: &str, value: &str) -> EnvVarArg {
        EnvVarArg {
            key: key.to_string(),
            value: value.to_string(),
        }
    }
}