Skip to main content

grab/
input.rs

1use std::{convert::TryFrom, fmt, io, str::FromStr};
2
3use crate::{
4    builder::{Builder, Config},
5    error::{access::AccessError, input::InputError},
6    parsers::InputType,
7};
8
9/// Represents some kind of input source which can be read from.
10#[derive(Debug)]
11pub struct Input {
12    kind: InputType,
13}
14
15impl Input {
16    /// This structure cannot be directly created, instead you may create and configure a builder
17    /// which can then be used to generate Input.
18    ///
19    /// Alternatively, you can use [Config::with_defaults] to parse some input using the default [Config].
20    pub fn builder() -> Builder {
21        Builder::default()
22    }
23
24    /// Attempt to locate an input source with the default configuration from the given input
25    pub fn with_defaults(input: impl AsRef<str>) -> Result<Self, InputError> {
26        Config::default().parse(input.as_ref())
27    }
28
29    /// Attempt to access the input source. Note that this function may block, depending on the
30    /// what underlying input source is.
31    pub fn access(&self) -> Result<InputReader, AccessError> {
32        Read::try_from(&self.kind).map(InputReader::new)
33    }
34
35    pub(crate) fn from_input_type(i: InputType) -> Self {
36        Self { kind: i }
37    }
38}
39
40impl FromStr for Input {
41    type Err = InputError;
42
43    fn from_str(s: &str) -> Result<Self, Self::Err> {
44        Self::with_defaults(s)
45    }
46}
47
48/// An opaque handle that implements std::io::Read
49#[derive(Debug)]
50pub struct InputReader {
51    input: Read,
52}
53
54impl InputReader {
55    fn new(input: Read) -> Self {
56        Self { input }
57    }
58
59    /// Convenience function for reading all the available input into a String. This function
60    /// internally contains similar semantics to [read_to_string][io::Read::read_to_string],
61    /// notably it will not consume the buffer in the case of a UTF8 error.
62    pub fn read_to_string(&mut self) -> Result<String, io::Error> {
63        let mut buf = String::new();
64
65        io::Read::read_to_string(&mut self.input, &mut buf)?;
66
67        Ok(buf)
68    }
69}
70
71impl io::Read for InputReader {
72    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
73        io::Read::read(&mut self.input, buf)
74    }
75}
76
77enum Read {
78    File(std::fs::File),
79    Stdin(std::io::Stdin),
80    Text(io::Cursor<String>),
81}
82
83impl Read {
84    fn stdin() -> Self {
85        Self::Stdin(io::stdin())
86    }
87
88    fn file(f: std::fs::File) -> Self {
89        Self::File(f)
90    }
91
92    fn text(s: impl AsRef<str>) -> Self {
93        let s = s.as_ref().to_string();
94
95        Self::Text(io::Cursor::new(s))
96    }
97}
98
99impl TryFrom<&InputType> for Read {
100    type Error = AccessError;
101
102    fn try_from(kind: &InputType) -> Result<Self, Self::Error> {
103        match kind {
104            InputType::Stdin => Ok(Read::stdin()),
105            InputType::File(ref f) => std::fs::File::open(f.path.as_path())
106                .map(Read::file)
107                .map_err(|e| AccessError::file_with_context(e, f.path.as_path())),
108            InputType::UTF8(ref s) => Ok(Self::text(s)),
109        }
110    }
111}
112
113impl io::Read for Read {
114    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
115        use Read::*;
116        match self {
117            File(ref mut file) => io::Read::read(file, buf),
118            Stdin(ref mut stdin) => io::Read::read(stdin, buf),
119            Text(ref mut cursor) => io::Read::read(cursor, buf),
120        }
121    }
122}
123
124impl fmt::Debug for Read {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        use Read::*;
127        let mut dbg = f.debug_struct("Read");
128
129        match self {
130            File(f) => dbg.field("file", &f),
131            Stdin(s) => dbg.field("stdin", &s),
132            Text(t) => dbg.field("cursor", &t),
133        };
134
135        dbg.finish()
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn input_from_default() {
145        let input = "@/some/file/path";
146        let res = Input::with_defaults(input);
147
148        assert!(res.is_ok())
149    }
150
151    #[test]
152    fn input_from_str() {
153        let input = "some text";
154        let res = Input::from_str(input);
155
156        assert!(res.is_ok())
157    }
158
159    #[test]
160    fn input_reader() {
161        let input = "some random text";
162        let i = Input::with_defaults(input).unwrap();
163
164        let output = i.access().unwrap().read_to_string().unwrap();
165
166        assert_eq!(input, output.as_str())
167    }
168}