1use crate::{
8 error::input::InputError,
9 input::Input,
10 parsers::{File, InputType, Parser, Stdin, Text, WeightedParser as WP},
11};
12
13use std::{ffi::OsStr, fmt};
14
15#[derive(Clone)]
21pub struct Config {
22 inner: Builder,
23}
24
25impl Config {
26 pub fn parse(&self, input: &str) -> Result<Input, InputError> {
28 self.parse_str(input).map(Input::from_input_type)
29 }
30
31 pub fn parse_os(&self, input: &OsStr) -> Result<Input, InputError> {
34 self.parse_os_str(input).map(Input::from_input_type)
35 }
36
37 fn with_parsers<F, R>(&self, f: F) -> R
40 where
41 F: FnMut(&[Option<&dyn WP>]) -> R,
42 {
43 let b = &self.inner;
44 let mut callback = f;
45
46 let mut list = [
47 b.file.as_ref().map(|p| p as &dyn WP),
48 b.stdin.as_ref().map(|p| p as &dyn WP),
49 b.text.as_ref().map(|p| p as &dyn WP),
50 ];
51
52 list.sort_by_key(|opt| opt.map(|p| p.weight()));
55
56 callback(&list)
57 }
58
59 fn apply<'a, F, I>(&self, parsers: I, mut f: F) -> Result<InputType, InputError>
65 where
66 F: FnMut(&dyn WP) -> Result<InputType, InputError>,
67 I: IntoIterator<Item = &'a dyn WP>,
68 {
69 let mut error: Option<InputError> = None;
70
71 for parser in parsers {
72 match f(parser) {
73 Ok(success) => return Ok(success),
74 Err(e) => match error {
75 Some(ref mut prev) => {
76 prev.extend(e);
77 }
78 None => error = Some(e),
79 },
80 }
81 }
82
83 Err(error.expect("Config should never have less than one parser, this is a bug"))
84 }
85}
86
87impl Parser for Config {
88 fn parse_str(&self, input: &str) -> Result<InputType, InputError> {
89 self.with_parsers(|parsers| {
90 let iter = parsers.iter().filter_map(|o| *o);
91 self.apply(iter, |p| p.parse_str(input))
92 })
93 }
94
95 fn parse_os_str(&self, input: &OsStr) -> Result<InputType, InputError> {
96 self.with_parsers(|parsers| {
97 let iter = parsers.iter().filter_map(|o| *o);
98 self.apply(iter, |p| p.parse_os_str(input))
99 })
100 }
101
102 fn parse_bytes(&self, input: &[u8]) -> Result<InputType, InputError> {
103 self.with_parsers(|parsers| {
104 let iter = parsers.iter().filter_map(|o| *o);
105 self.apply(iter, |p| p.parse_bytes(input))
106 })
107 }
108}
109
110impl fmt::Debug for Config {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 let mut dbg = f.debug_struct("Config");
113
114 if let Some(text) = &self.inner.text {
115 dbg.field("text", &text);
116 }
117
118 if let Some(stdin) = &self.inner.stdin {
119 dbg.field("stdin", &stdin);
120 }
121
122 if let Some(file) = &self.inner.file {
123 dbg.field("file", &file);
124 }
125
126 dbg.finish()
127 }
128}
129
130impl Default for Config {
131 fn default() -> Self {
132 let cfg = Builder::new().with(|b| b.text().stdin().file());
133
134 debug_assert!(cfg.is_valid());
135
136 Self { inner: cfg }
137 }
138}
139
140#[derive(Debug, Clone, Default)]
146pub struct Builder {
147 stdin: Option<Stdin>,
148 file: Option<File>,
149 text: Option<Text>,
150}
151
152impl Builder {
153 pub fn new() -> Self {
155 Self::default()
156 }
157
158 pub fn with<F>(self, f: F) -> Self
160 where
161 F: FnMut(&mut Self) -> &mut Self,
162 {
163 let mut this = self;
164 let mut actions = f;
165
166 actions(&mut this);
167
168 this
169 }
170
171 pub fn build(self) -> Config {
178 assert!(
179 self.is_valid(),
180 "A grab::Builder must contain at least one parser"
181 );
182
183 Config { inner: self }
184 }
185
186 pub fn try_build(self) -> Result<Config, Self> {
192 if self.is_valid() {
193 return Ok(Config { inner: self });
194 }
195
196 Err(self)
197 }
198
199 pub fn text(&mut self) -> &mut Self {
201 self.with_text(Text::new())
202 }
203
204 pub fn with_text(&mut self, t: Text) -> &mut Self {
206 self.text = Some(t);
207
208 self
209 }
210
211 pub fn stdin(&mut self) -> &mut Self {
213 self.with_stdin(Stdin::new())
214 }
215
216 pub fn with_stdin(&mut self, s: Stdin) -> &mut Self {
218 self.stdin = Some(s);
219
220 self
221 }
222
223 pub fn file(&mut self) -> &mut Self {
225 self.with_file(File::new())
226 }
227
228 pub fn with_file(&mut self, f: File) -> &mut Self {
230 self.file = Some(f);
231
232 self
233 }
234
235 pub fn is_valid(&self) -> bool {
237 let b = self;
238
239 b.text.is_some() || b.stdin.is_some() || b.file.is_some()
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn config_default_is_valid() {
249 let _cfg = Config::default();
250 }
251
252 #[test]
253 fn builder_set_text() {
254 let b = Builder::new().with(|this| this.text());
255
256 assert!(b.text.is_some())
257 }
258
259 #[test]
260 fn builder_set_file() {
261 let b = Builder::new().with(|this| this.file());
262
263 assert!(b.file.is_some())
264 }
265
266 #[test]
267 fn builder_set_stdin() {
268 let b = Builder::new().with(|this| this.stdin());
269
270 assert!(b.stdin.is_some())
271 }
272
273 #[test]
274 fn sorted_by_weight_ascending() {
275 let cfg = Config::default();
276
277 let mut last = 0;
278 cfg.with_parsers(|list| {
279 for wp in list.iter().filter_map(|o| *o) {
281 let weight = wp.weight();
282
283 assert!(weight >= last);
284
285 last = weight;
286 }
287 })
288 }
289
290 #[test]
291 fn config_default_parse_stdin() {
292 let input = "-";
293 let cfg = Config::default();
294
295 let t = cfg.parse_str(input).expect("a successful parse");
296
297 match t {
298 InputType::Stdin => {}
299 bad => panic!("expected Stdin, got: {:?}", bad),
300 }
301 }
302
303 #[test]
304 fn config_default_parse_file() {
305 let input = "@some/relative/path";
306 let cfg = Config::default();
307
308 let t = cfg.parse_str(input).expect("a successful parse");
309
310 match t {
311 InputType::File(_) => {}
312 bad => panic!("expected File, got: {:?}", bad),
313 }
314 }
315
316 #[test]
317 fn config_default_parse_text() {
318 let input = "basic textual input";
319 let cfg = Config::default();
320
321 let t = cfg.parse_str(input).expect("a successful parse");
322
323 match t {
324 InputType::UTF8(_) => {}
325 bad => panic!("expected Text, got: {:?}", bad),
326 }
327 }
328}