standout_input/sources/
arg.rs1use 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}