on 384ea38 help sign in
bat/src/input.rs
274 lines 7.9 kB history raw
1use std::ffi::{OsStr, OsString};
2use std::fs::File;
3use std::io::{self, BufRead, BufReader, Read};
5use content_inspector::{self, ContentType};
7use crate::error::*;
9const THEME_PREVIEW_FILE: &[u8] = include_bytes!("../assets/theme_preview.rs");
11/// A description for an Input source.
12/// This tells bat how to refer to the input.
13#[derive(Debug, Clone)]
14pub(crate) struct InputDescription {
15 /// The name of the input used when printing warnings.
16 pub full: String,
18 /// The prefix string for the input when displaying it in the header.
19 pub prefix: String,
21 /// The name of the input.
22 pub name: String,
25pub(crate) enum InputKind<'a> {
26 OrdinaryFile(OsString),
27 StdIn,
28 ThemePreviewFile,
29 CustomReader(Box<dyn Read + 'a>),
32#[derive(Clone, Default)]
33pub(crate) struct InputMetadata {
34 pub(crate) user_provided_name: Option<OsString>,
37pub struct Input<'a> {
38 pub(crate) kind: InputKind<'a>,
39 pub(crate) metadata: InputMetadata,
42pub(crate) enum OpenedInputKind {
43 OrdinaryFile(OsString),
44 StdIn,
45 ThemePreviewFile,
46 CustomReader,
49impl OpenedInputKind {
50 pub(crate) fn is_theme_preview_file(&self) -> bool {
51 match self {
52 OpenedInputKind::ThemePreviewFile => true,
53 _ => false,
54 }
55 }
58pub(crate) struct OpenedInput<'a> {
59 pub(crate) kind: OpenedInputKind,
60 pub(crate) metadata: InputMetadata,
61 pub(crate) reader: InputReader<'a>,
64impl<'a> Input<'a> {
65 pub fn ordinary_file(path: &OsStr) -> Self {
66 Input {
67 kind: InputKind::OrdinaryFile(path.to_os_string()),
68 metadata: InputMetadata::default(),
69 }
70 }
72 pub fn stdin() -> Self {
73 Input {
74 kind: InputKind::StdIn,
75 metadata: InputMetadata::default(),
76 }
77 }
79 pub fn theme_preview_file() -> Self {
80 Input {
81 kind: InputKind::ThemePreviewFile,
82 metadata: InputMetadata::default(),
83 }
84 }
86 pub fn from_reader(reader: Box<dyn Read + 'a>) -> Self {
87 Input {
88 kind: InputKind::CustomReader(reader),
89 metadata: InputMetadata::default(),
90 }
91 }
93 pub fn is_stdin(&self) -> bool {
94 if let InputKind::StdIn = self.kind {
95 true
96 } else {
97 false
98 }
99 }
101 pub fn with_name(mut self, provided_name: Option<&OsStr>) -> Self {
102 self.metadata.user_provided_name = provided_name.map(|n| n.to_owned());
103 self
104 }
106 pub(crate) fn open<R: BufRead + 'a>(self, stdin: R) -> Result<OpenedInput<'a>> {
107 match self.kind {
108 InputKind::StdIn => Ok(OpenedInput {
109 kind: OpenedInputKind::StdIn,
110 metadata: self.metadata,
111 reader: InputReader::new(stdin),
112 }),
113 InputKind::OrdinaryFile(path) => Ok(OpenedInput {
114 kind: OpenedInputKind::OrdinaryFile(path.clone()),
115 metadata: self.metadata,
116 reader: {
117 let file = File::open(&path)
118 .map_err(|e| format!("'{}': {}", path.to_string_lossy(), e))?;
119 if file.metadata()?.is_dir() {
120 return Err(format!("'{}' is a directory.", path.to_string_lossy()).into());
121 }
122 InputReader::new(BufReader::new(file))
123 },
124 }),
125 InputKind::ThemePreviewFile => Ok(OpenedInput {
126 kind: OpenedInputKind::ThemePreviewFile,
127 metadata: self.metadata,
128 reader: InputReader::new(THEME_PREVIEW_FILE),
129 }),
130 InputKind::CustomReader(reader) => Ok(OpenedInput {
131 kind: OpenedInputKind::CustomReader,
132 metadata: self.metadata,
133 reader: InputReader::new(BufReader::new(reader)),
134 }),
135 }
136 }
139impl<'a> OpenedInput<'a> {
140 pub fn description(&self) -> InputDescription {
141 if let Some(ref name) = self.metadata.user_provided_name {
142 InputDescription {
143 full: format!("file '{}'", name.to_string_lossy()),
144 prefix: "File: ".to_owned(),
145 name: name.to_string_lossy().into_owned(),
146 }
147 } else {
148 match self.kind {
149 OpenedInputKind::OrdinaryFile(ref path) => InputDescription {
150 full: format!("file '{}'", path.to_string_lossy()),
151 prefix: "File: ".to_owned(),
152 name: path.to_string_lossy().into_owned(),
153 },
154 OpenedInputKind::StdIn => InputDescription {
155 full: "STDIN".to_owned(),
156 prefix: "".to_owned(),
157 name: "STDIN".to_owned(),
158 },
159 OpenedInputKind::ThemePreviewFile => InputDescription {
160 full: "".to_owned(),
161 prefix: "".to_owned(),
162 name: "".to_owned(),
163 },
164 OpenedInputKind::CustomReader => InputDescription {
165 full: "reader".to_owned(),
166 prefix: "".to_owned(),
167 name: "READER".into(),
168 },
169 }
170 }
171 }
174pub(crate) struct InputReader<'a> {
175 inner: Box<dyn BufRead + 'a>,
176 pub(crate) first_line: Vec<u8>,
177 pub(crate) content_type: Option<ContentType>,
180impl<'a> InputReader<'a> {
181 fn new<R: BufRead + 'a>(mut reader: R) -> InputReader<'a> {
182 let mut first_line = vec![];
183 reader.read_until(b'\n', &mut first_line).ok();
185 let content_type = if first_line.is_empty() {
186 None
187 } else {
188 Some(content_inspector::inspect(&first_line[..]))
189 };
191 if content_type == Some(ContentType::UTF_16LE) {
192 reader.read_until(0x00, &mut first_line).ok();
193 }
195 InputReader {
196 inner: Box::new(reader),
197 first_line,
198 content_type,
199 }
200 }
202 pub(crate) fn read_line(&mut self, buf: &mut Vec<u8>) -> io::Result<bool> {
203 if self.first_line.is_empty() {
204 let res = self.inner.read_until(b'\n', buf).map(|size| size > 0)?;
206 if self.content_type == Some(ContentType::UTF_16LE) {
207 self.inner.read_until(0x00, buf).ok();
208 }
210 Ok(res)
211 } else {
212 buf.append(&mut self.first_line);
213 Ok(true)
214 }
215 }
218#[test]
219fn basic() {
220 let content = b"#!/bin/bash\necho hello";
221 let mut reader = InputReader::new(&content[..]);
223 assert_eq!(b"#!/bin/bash\n", &reader.first_line[..]);
225 let mut buffer = vec![];
227 let res = reader.read_line(&mut buffer);
228 assert!(res.is_ok());
229 assert_eq!(true, res.unwrap());
230 assert_eq!(b"#!/bin/bash\n", &buffer[..]);
232 buffer.clear();
234 let res = reader.read_line(&mut buffer);
235 assert!(res.is_ok());
236 assert_eq!(true, res.unwrap());
237 assert_eq!(b"echo hello", &buffer[..]);
239 buffer.clear();
241 let res = reader.read_line(&mut buffer);
242 assert!(res.is_ok());
243 assert_eq!(false, res.unwrap());
244 assert!(buffer.is_empty());
247#[test]
248fn utf16le() {
249 let content = b"\xFF\xFE\x73\x00\x0A\x00\x64\x00";
250 let mut reader = InputReader::new(&content[..]);
252 assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &reader.first_line[..]);
254 let mut buffer = vec![];
256 let res = reader.read_line(&mut buffer);
257 assert!(res.is_ok());
258 assert_eq!(true, res.unwrap());
259 assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &buffer[..]);
261 buffer.clear();
263 let res = reader.read_line(&mut buffer);
264 assert!(res.is_ok());
265 assert_eq!(true, res.unwrap());
266 assert_eq!(b"\x64\x00", &buffer[..]);
268 buffer.clear();
270 let res = reader.read_line(&mut buffer);
271 assert!(res.is_ok());
272 assert_eq!(false, res.unwrap());
273 assert!(buffer.is_empty());