standout_input/sources/
arg.rs1use clap::ArgMatches;
4
5use crate::collector::{InputCollector, InputSourceKind, ResolvedInput};
6use crate::InputError;
7
8#[derive(Debug, Clone)]
23pub struct ArgSource {
24 name: String,
25}
26
27impl ArgSource {
28 pub fn new(name: impl Into<String>) -> Self {
32 Self { name: name.into() }
33 }
34
35 pub fn arg_name(&self) -> &str {
37 &self.name
38 }
39}
40
41impl InputCollector<String> for ArgSource {
42 fn name(&self) -> &'static str {
43 "argument"
44 }
45
46 fn is_available(&self, matches: &ArgMatches) -> bool {
47 matches.contains_id(&self.name) && matches.get_one::<String>(&self.name).is_some()
48 }
49
50 fn collect(&self, matches: &ArgMatches) -> Result<Option<String>, InputError> {
51 Ok(matches.get_one::<String>(&self.name).cloned())
52 }
53}
54
55#[derive(Debug, Clone)]
70pub struct FlagSource {
71 name: String,
72 invert: bool,
73}
74
75impl FlagSource {
76 pub fn new(name: impl Into<String>) -> Self {
80 Self {
81 name: name.into(),
82 invert: false,
83 }
84 }
85
86 pub fn inverted(mut self) -> Self {
91 self.invert = true;
92 self
93 }
94
95 pub fn flag_name(&self) -> &str {
97 &self.name
98 }
99}
100
101impl InputCollector<bool> for FlagSource {
102 fn name(&self) -> &'static str {
103 "flag"
104 }
105
106 fn is_available(&self, matches: &ArgMatches) -> bool {
107 matches.contains_id(&self.name)
109 }
110
111 fn collect(&self, matches: &ArgMatches) -> Result<Option<bool>, InputError> {
112 let value = matches.get_flag(&self.name);
113 let result = if self.invert { !value } else { value };
114
115 if matches.get_flag(&self.name) {
118 Ok(Some(result))
119 } else {
120 Ok(None)
121 }
122 }
123}
124
125impl FlagSource {
127 pub fn resolve(&self, matches: &ArgMatches) -> Result<ResolvedInput<bool>, InputError> {
129 let value = matches.get_flag(&self.name);
130 let result = if self.invert { !value } else { value };
131
132 Ok(ResolvedInput {
133 value: result,
134 source: InputSourceKind::Flag,
135 })
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use clap::{Arg, Command};
143
144 fn make_matches(args: &[&str]) -> ArgMatches {
145 Command::new("test")
146 .arg(Arg::new("message").long("message").short('m'))
147 .arg(
148 Arg::new("verbose")
149 .long("verbose")
150 .short('v')
151 .action(clap::ArgAction::SetTrue),
152 )
153 .arg(
154 Arg::new("no-editor")
155 .long("no-editor")
156 .action(clap::ArgAction::SetTrue),
157 )
158 .try_get_matches_from(args)
159 .unwrap()
160 }
161
162 #[test]
163 fn arg_source_available_when_provided() {
164 let matches = make_matches(&["test", "--message", "hello"]);
165 let source = ArgSource::new("message");
166
167 assert!(source.is_available(&matches));
168 assert_eq!(source.collect(&matches).unwrap(), Some("hello".to_string()));
169 }
170
171 #[test]
172 fn arg_source_unavailable_when_missing() {
173 let matches = make_matches(&["test"]);
174 let source = ArgSource::new("message");
175
176 assert!(!source.is_available(&matches));
177 assert_eq!(source.collect(&matches).unwrap(), None);
178 }
179
180 #[test]
181 fn flag_source_returns_some_when_set() {
182 let matches = make_matches(&["test", "--verbose"]);
183 let source = FlagSource::new("verbose");
184
185 assert!(source.is_available(&matches));
186 assert_eq!(source.collect(&matches).unwrap(), Some(true));
187 }
188
189 #[test]
190 fn flag_source_returns_none_when_not_set() {
191 let matches = make_matches(&["test"]);
192 let source = FlagSource::new("verbose");
193
194 assert!(source.is_available(&matches));
196 assert_eq!(source.collect(&matches).unwrap(), None);
197 }
198
199 #[test]
200 fn flag_source_inverted() {
201 let matches = make_matches(&["test", "--no-editor"]);
202 let source = FlagSource::new("no-editor").inverted();
203
204 assert_eq!(source.collect(&matches).unwrap(), Some(false));
206 }
207
208 #[test]
209 fn flag_source_inverted_not_set() {
210 let matches = make_matches(&["test"]);
211 let source = FlagSource::new("no-editor").inverted();
212
213 assert_eq!(source.collect(&matches).unwrap(), None);
215 }
216}