bat_impl/
input.rs

1use std::convert::TryFrom;
2use std::fs;
3use std::fs::File;
4use std::io::{self, BufRead, BufReader, Read};
5use std::path::{Path, PathBuf};
6
7use clircle::{Clircle, Identifier};
8use content_inspector::{self, ContentType};
9
10use crate::error::*;
11
12/// A description of an Input source.
13/// This tells bat how to refer to the input.
14#[derive(Clone)]
15pub struct InputDescription {
16    pub(crate) name: String,
17
18    /// The input title.
19    /// This replaces the name if provided.
20    title: Option<String>,
21
22    /// The input kind.
23    kind: Option<String>,
24
25    /// A summary description of the input.
26    /// Defaults to "{kind} '{name}'"
27    summary: Option<String>,
28}
29
30impl InputDescription {
31    /// Creates a description for an input.
32    pub fn new(name: impl Into<String>) -> Self {
33        InputDescription {
34            name: name.into(),
35            title: None,
36            kind: None,
37            summary: None,
38        }
39    }
40
41    pub fn set_kind(&mut self, kind: Option<String>) {
42        self.kind = kind;
43    }
44
45    pub fn set_summary(&mut self, summary: Option<String>) {
46        self.summary = summary;
47    }
48
49    pub fn set_title(&mut self, title: Option<String>) {
50        self.title = title;
51    }
52
53    pub fn title(&self) -> &String {
54        match &self.title {
55            Some(title) => title,
56            None => &self.name,
57        }
58    }
59
60    pub fn kind(&self) -> Option<&String> {
61        self.kind.as_ref()
62    }
63
64    pub fn summary(&self) -> String {
65        self.summary.clone().unwrap_or_else(|| match &self.kind {
66            None => self.name.clone(),
67            Some(kind) => format!("{} '{}'", kind.to_lowercase(), self.name),
68        })
69    }
70}
71
72pub(crate) enum InputKind<'a> {
73    OrdinaryFile(PathBuf),
74    StdIn,
75    CustomReader(Box<dyn Read + 'a>),
76}
77
78impl<'a> InputKind<'a> {
79    pub fn description(&self) -> InputDescription {
80        match self {
81            InputKind::OrdinaryFile(ref path) => InputDescription::new(path.to_string_lossy()),
82            InputKind::StdIn => InputDescription::new("STDIN"),
83            InputKind::CustomReader(_) => InputDescription::new("READER"),
84        }
85    }
86}
87
88#[derive(Clone, Default)]
89pub(crate) struct InputMetadata {
90    pub(crate) user_provided_name: Option<PathBuf>,
91    pub(crate) size: Option<u64>,
92}
93
94pub struct Input<'a> {
95    pub(crate) kind: InputKind<'a>,
96    pub(crate) metadata: InputMetadata,
97    pub(crate) description: InputDescription,
98}
99
100pub(crate) enum OpenedInputKind {
101    OrdinaryFile(PathBuf),
102    StdIn,
103    CustomReader,
104}
105
106pub(crate) struct OpenedInput<'a> {
107    pub(crate) kind: OpenedInputKind,
108    pub(crate) metadata: InputMetadata,
109    pub(crate) reader: InputReader<'a>,
110    pub(crate) description: InputDescription,
111}
112
113impl OpenedInput<'_> {
114    /// Get the path of the file:
115    /// If this was set by the metadata, that will take priority.
116    /// If it wasn't, it will use the real file path (if available).
117    pub(crate) fn path(&self) -> Option<&PathBuf> {
118        self.metadata
119            .user_provided_name
120            .as_ref()
121            .or(match self.kind {
122                OpenedInputKind::OrdinaryFile(ref path) => Some(path),
123                _ => None,
124            })
125    }
126}
127
128impl<'a> Input<'a> {
129    pub fn ordinary_file(path: impl AsRef<Path>) -> Self {
130        Self::_ordinary_file(path.as_ref())
131    }
132
133    fn _ordinary_file(path: &Path) -> Self {
134        let kind = InputKind::OrdinaryFile(path.to_path_buf());
135        let metadata = InputMetadata {
136            size: fs::metadata(path).map(|m| m.len()).ok(),
137            ..InputMetadata::default()
138        };
139
140        Input {
141            description: kind.description(),
142            metadata,
143            kind,
144        }
145    }
146
147    pub fn stdin() -> Self {
148        let kind = InputKind::StdIn;
149        Input {
150            description: kind.description(),
151            metadata: InputMetadata::default(),
152            kind,
153        }
154    }
155
156    pub fn from_reader(reader: Box<dyn Read + 'a>) -> Self {
157        let kind = InputKind::CustomReader(reader);
158        Input {
159            description: kind.description(),
160            metadata: InputMetadata::default(),
161            kind,
162        }
163    }
164
165    pub fn is_stdin(&self) -> bool {
166        matches!(self.kind, InputKind::StdIn)
167    }
168
169    pub fn with_name(self, provided_name: Option<impl AsRef<Path>>) -> Self {
170        self._with_name(provided_name.as_ref().map(|it| it.as_ref()))
171    }
172
173    fn _with_name(mut self, provided_name: Option<&Path>) -> Self {
174        if let Some(name) = provided_name {
175            self.description.name = name.to_string_lossy().to_string()
176        }
177
178        self.metadata.user_provided_name = provided_name.map(|n| n.to_owned());
179        self
180    }
181
182    pub fn description(&self) -> &InputDescription {
183        &self.description
184    }
185
186    pub fn description_mut(&mut self) -> &mut InputDescription {
187        &mut self.description
188    }
189
190    pub(crate) fn open<R: BufRead + 'a>(
191        self,
192        stdin: R,
193        stdout_identifier: Option<&Identifier>,
194    ) -> Result<OpenedInput<'a>> {
195        let description = self.description().clone();
196        match self.kind {
197            InputKind::StdIn => {
198                if let Some(stdout) = stdout_identifier {
199                    let input_identifier = Identifier::try_from(clircle::Stdio::Stdin)
200                        .map_err(|e| format!("Stdin: Error identifying file: {}", e))?;
201                    if stdout.surely_conflicts_with(&input_identifier) {
202                        return Err("IO circle detected. The input from stdin is also an output. Aborting to avoid infinite loop.".into());
203                    }
204                }
205
206                Ok(OpenedInput {
207                    kind: OpenedInputKind::StdIn,
208                    description,
209                    metadata: self.metadata,
210                    reader: InputReader::new(stdin),
211                })
212            }
213
214            InputKind::OrdinaryFile(path) => Ok(OpenedInput {
215                kind: OpenedInputKind::OrdinaryFile(path.clone()),
216                description,
217                metadata: self.metadata,
218                reader: {
219                    let mut file = File::open(&path)
220                        .map_err(|e| format!("'{}': {}", path.to_string_lossy(), e))?;
221                    if file.metadata()?.is_dir() {
222                        return Err(format!("'{}' is a directory.", path.to_string_lossy()).into());
223                    }
224
225                    if let Some(stdout) = stdout_identifier {
226                        let input_identifier = Identifier::try_from(file).map_err(|e| {
227                            format!("{}: Error identifying file: {}", path.to_string_lossy(), e)
228                        })?;
229                        if stdout.surely_conflicts_with(&input_identifier) {
230                            return Err(format!(
231                                "IO circle detected. The input from '{}' is also an output. Aborting to avoid infinite loop.",
232                                path.to_string_lossy()
233                            )
234                            .into());
235                        }
236                        file = input_identifier.into_inner().expect("The file was lost in the clircle::Identifier, this should not have happened...");
237                    }
238
239                    InputReader::new(BufReader::new(file))
240                },
241            }),
242            InputKind::CustomReader(reader) => Ok(OpenedInput {
243                description,
244                kind: OpenedInputKind::CustomReader,
245                metadata: self.metadata,
246                reader: InputReader::new(BufReader::new(reader)),
247            }),
248        }
249    }
250}
251
252pub(crate) struct InputReader<'a> {
253    inner: Box<dyn BufRead + 'a>,
254    pub(crate) first_line: Vec<u8>,
255    pub(crate) content_type: Option<ContentType>,
256}
257
258impl<'a> InputReader<'a> {
259    fn new<R: BufRead + 'a>(mut reader: R) -> InputReader<'a> {
260        let mut first_line = vec![];
261        reader.read_until(b'\n', &mut first_line).ok();
262
263        let content_type = if first_line.is_empty() {
264            None
265        } else {
266            Some(content_inspector::inspect(&first_line[..]))
267        };
268
269        if content_type == Some(ContentType::UTF_16LE) {
270            reader.read_until(0x00, &mut first_line).ok();
271        }
272
273        InputReader {
274            inner: Box::new(reader),
275            first_line,
276            content_type,
277        }
278    }
279
280    pub(crate) fn read_line(&mut self, buf: &mut Vec<u8>) -> io::Result<bool> {
281        if !self.first_line.is_empty() {
282            buf.append(&mut self.first_line);
283            return Ok(true);
284        }
285
286        let res = self.inner.read_until(b'\n', buf).map(|size| size > 0)?;
287
288        if self.content_type == Some(ContentType::UTF_16LE) {
289            let _ = self.inner.read_until(0x00, buf);
290        }
291
292        Ok(res)
293    }
294}
295
296#[test]
297fn basic() {
298    let content = b"#!/bin/bash\necho hello";
299    let mut reader = InputReader::new(&content[..]);
300
301    assert_eq!(b"#!/bin/bash\n", &reader.first_line[..]);
302
303    let mut buffer = vec![];
304
305    let res = reader.read_line(&mut buffer);
306    assert!(res.is_ok());
307    assert!(res.unwrap());
308    assert_eq!(b"#!/bin/bash\n", &buffer[..]);
309
310    buffer.clear();
311
312    let res = reader.read_line(&mut buffer);
313    assert!(res.is_ok());
314    assert!(res.unwrap());
315    assert_eq!(b"echo hello", &buffer[..]);
316
317    buffer.clear();
318
319    let res = reader.read_line(&mut buffer);
320    assert!(res.is_ok());
321    assert!(!res.unwrap());
322    assert!(buffer.is_empty());
323}
324
325#[test]
326fn utf16le() {
327    let content = b"\xFF\xFE\x73\x00\x0A\x00\x64\x00";
328    let mut reader = InputReader::new(&content[..]);
329
330    assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &reader.first_line[..]);
331
332    let mut buffer = vec![];
333
334    let res = reader.read_line(&mut buffer);
335    assert!(res.is_ok());
336    assert!(res.unwrap());
337    assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &buffer[..]);
338
339    buffer.clear();
340
341    let res = reader.read_line(&mut buffer);
342    assert!(res.is_ok());
343    assert!(res.unwrap());
344    assert_eq!(b"\x64\x00", &buffer[..]);
345
346    buffer.clear();
347
348    let res = reader.read_line(&mut buffer);
349    assert!(res.is_ok());
350    assert!(!res.unwrap());
351    assert!(buffer.is_empty());
352}