Skip to main content

standout_input/sources/
arg.rs

1use clap::ArgMatches;
2
3use crate::collector::{InputCollector, InputSourceKind, ResolvedInput};
4use crate::InputError;
5
6#[derive(Debug, Clone)]
7pub struct ArgSource {
8    name: String,
9}
10
11impl ArgSource {
12    pub fn new(name: impl Into<String>) -> Self {
13        Self { name: name.into() }
14    }
15
16    pub fn arg_name(&self) -> &str {
17        &self.name
18    }
19}
20
21impl InputCollector<String> for ArgSource {
22    fn name(&self) -> &'static str {
23        "argument"
24    }
25
26    fn is_available(&self, matches: &ArgMatches) -> bool {
27        matches.contains_id(&self.name) && matches.get_one::<String>(&self.name).is_some()
28    }
29
30    fn collect(&self, matches: &ArgMatches) -> Result<Option<String>, InputError> {
31        Ok(matches.get_one::<String>(&self.name).cloned())
32    }
33}
34
35#[derive(Debug, Clone)]
36pub struct FlagSource {
37    name: String,
38    invert: bool,
39}
40
41impl FlagSource {
42    pub fn new(name: impl Into<String>) -> Self {
43        Self {
44            name: name.into(),
45            invert: false,
46        }
47    }
48
49    pub fn inverted(mut self) -> Self {
50        self.invert = true;
51        self
52    }
53
54    pub fn flag_name(&self) -> &str {
55        &self.name
56    }
57}
58
59impl InputCollector<bool> for FlagSource {
60    fn name(&self) -> &'static str {
61        "flag"
62    }
63
64    fn is_available(&self, matches: &ArgMatches) -> bool {
65        matches.contains_id(&self.name)
66    }
67
68    fn collect(&self, matches: &ArgMatches) -> Result<Option<bool>, InputError> {
69        let value = matches.get_flag(&self.name);
70        let result = if self.invert { !value } else { value };
71
72        if matches.get_flag(&self.name) {
73            Ok(Some(result))
74        } else {
75            Ok(None)
76        }
77    }
78}
79
80impl FlagSource {
81    pub fn resolve(&self, matches: &ArgMatches) -> Result<ResolvedInput<bool>, InputError> {
82        let value = matches.get_flag(&self.name);
83        let result = if self.invert { !value } else { value };
84
85        Ok(ResolvedInput {
86            value: result,
87            source: InputSourceKind::Flag,
88        })
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use clap::{Arg, Command};
96
97    fn make_matches(args: &[&str]) -> ArgMatches {
98        Command::new("test")
99            .arg(Arg::new("message").long("message").short('m'))
100            .arg(
101                Arg::new("verbose")
102                    .long("verbose")
103                    .short('v')
104                    .action(clap::ArgAction::SetTrue),
105            )
106            .arg(
107                Arg::new("no-editor")
108                    .long("no-editor")
109                    .action(clap::ArgAction::SetTrue),
110            )
111            .try_get_matches_from(args)
112            .unwrap()
113    }
114
115    #[test]
116    fn arg_source_available_when_provided() {
117        let matches = make_matches(&["test", "--message", "hello"]);
118        let source = ArgSource::new("message");
119
120        assert!(source.is_available(&matches));
121        assert_eq!(source.collect(&matches).unwrap(), Some("hello".to_string()));
122    }
123
124    #[test]
125    fn arg_source_unavailable_when_missing() {
126        let matches = make_matches(&["test"]);
127        let source = ArgSource::new("message");
128
129        assert!(!source.is_available(&matches));
130        assert_eq!(source.collect(&matches).unwrap(), None);
131    }
132
133    #[test]
134    fn flag_source_returns_some_when_set() {
135        let matches = make_matches(&["test", "--verbose"]);
136        let source = FlagSource::new("verbose");
137
138        assert!(source.is_available(&matches));
139        assert_eq!(source.collect(&matches).unwrap(), Some(true));
140    }
141
142    #[test]
143    fn flag_source_returns_none_when_not_set() {
144        let matches = make_matches(&["test"]);
145        let source = FlagSource::new("verbose");
146
147        assert!(source.is_available(&matches));
148        assert_eq!(source.collect(&matches).unwrap(), None);
149    }
150
151    #[test]
152    fn flag_source_inverted() {
153        let matches = make_matches(&["test", "--no-editor"]);
154        let source = FlagSource::new("no-editor").inverted();
155
156        assert_eq!(source.collect(&matches).unwrap(), Some(false));
157    }
158
159    #[test]
160    fn flag_source_inverted_not_set() {
161        let matches = make_matches(&["test"]);
162        let source = FlagSource::new("no-editor").inverted();
163
164        assert_eq!(source.collect(&matches).unwrap(), None);
165    }
166}