on v0.15.1 help sign in
bat/src/input.rs
258 lines 7.5 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#[derive(Debug, Clone)]
12pub(crate) struct InputDescription {
13 pub full: String,
14 pub prefix: String,
15 pub name: String,
18pub(crate) enum InputKind<'a> {
19 OrdinaryFile(OsString),
20 StdIn,
21 ThemePreviewFile,
22 CustomReader(Box<dyn Read + 'a>),
25#[derive(Clone, Default)]
26pub(crate) struct InputMetadata {
27 pub(crate) user_provided_name: Option<OsString>,
30pub struct Input<'a> {
31 pub(crate) kind: InputKind<'a>,
32 pub(crate) metadata: InputMetadata,
35pub(crate) enum OpenedInputKind {
36 OrdinaryFile(OsString),
37 StdIn,
38 ThemePreviewFile,
39 CustomReader,
42pub(crate) struct OpenedInput<'a> {
43 pub(crate) kind: OpenedInputKind,
44 pub(crate) metadata: InputMetadata,
45 pub(crate) reader: InputReader<'a>,
48impl<'a> Input<'a> {
49 pub fn ordinary_file(path: &OsStr) -> Self {
50 Input {
51 kind: InputKind::OrdinaryFile(path.to_os_string()),
52 metadata: InputMetadata::default(),
53 }
54 }
56 pub fn stdin() -> Self {
57 Input {
58 kind: InputKind::StdIn,
59 metadata: InputMetadata::default(),
60 }
61 }
63 pub fn theme_preview_file() -> Self {
64 Input {
65 kind: InputKind::ThemePreviewFile,
66 metadata: InputMetadata::default(),
67 }
68 }
70 pub fn from_reader(reader: Box<dyn Read + 'a>) -> Self {
71 Input {
72 kind: InputKind::CustomReader(reader),
73 metadata: InputMetadata::default(),
74 }
75 }
77 pub fn is_stdin(&self) -> bool {
78 if let InputKind::StdIn = self.kind {
79 true
80 } else {
81 false
82 }
83 }
85 pub fn with_name(mut self, provided_name: Option<&OsStr>) -> Self {
86 self.metadata.user_provided_name = provided_name.map(|n| n.to_owned());
87 self
88 }
90 pub(crate) fn open<R: BufRead + 'a>(self, stdin: R) -> Result<OpenedInput<'a>> {
91 match self.kind {
92 InputKind::StdIn => Ok(OpenedInput {
93 kind: OpenedInputKind::StdIn,
94 metadata: self.metadata,
95 reader: InputReader::new(stdin),
96 }),
97 InputKind::OrdinaryFile(path) => Ok(OpenedInput {
98 kind: OpenedInputKind::OrdinaryFile(path.clone()),
99 metadata: self.metadata,
100 reader: {
101 let file = File::open(&path)
102 .map_err(|e| format!("'{}': {}", path.to_string_lossy(), e))?;
103 if file.metadata()?.is_dir() {
104 return Err(format!("'{}' is a directory.", path.to_string_lossy()).into());
105 }
106 InputReader::new(BufReader::new(file))
107 },
108 }),
109 InputKind::ThemePreviewFile => Ok(OpenedInput {
110 kind: OpenedInputKind::ThemePreviewFile,
111 metadata: self.metadata,
112 reader: InputReader::new(THEME_PREVIEW_FILE),
113 }),
114 InputKind::CustomReader(reader) => Ok(OpenedInput {
115 kind: OpenedInputKind::CustomReader,
116 metadata: self.metadata,
117 reader: InputReader::new(BufReader::new(reader)),
118 }),
119 }
120 }
123impl<'a> OpenedInput<'a> {
124 pub fn description(&self) -> InputDescription {
125 if let Some(ref name) = self.metadata.user_provided_name {
126 InputDescription {
127 full: format!("file '{}'", name.to_string_lossy()),
128 prefix: "File: ".to_owned(),
129 name: name.to_string_lossy().into_owned(),
130 }
131 } else {
132 match self.kind {
133 OpenedInputKind::OrdinaryFile(ref path) => InputDescription {
134 full: format!("file '{}'", path.to_string_lossy()),
135 prefix: "File: ".to_owned(),
136 name: path.to_string_lossy().into_owned(),
137 },
138 OpenedInputKind::StdIn => InputDescription {
139 full: "STDIN".to_owned(),
140 prefix: "".to_owned(),
141 name: "STDIN".to_owned(),
142 },
143 OpenedInputKind::ThemePreviewFile => InputDescription {
144 full: "".to_owned(),
145 prefix: "".to_owned(),
146 name: "".to_owned(),
147 },
148 OpenedInputKind::CustomReader => InputDescription {
149 full: "reader".to_owned(),
150 prefix: "".to_owned(),
151 name: "READER".into(),
152 },
153 }
154 }
155 }
158pub(crate) struct InputReader<'a> {
159 inner: Box<dyn BufRead + 'a>,
160 pub(crate) first_line: Vec<u8>,
161 pub(crate) content_type: Option<ContentType>,
164impl<'a> InputReader<'a> {
165 fn new<R: BufRead + 'a>(mut reader: R) -> InputReader<'a> {
166 let mut first_line = vec![];
167 reader.read_until(b'\n', &mut first_line).ok();
169 let content_type = if first_line.is_empty() {
170 None
171 } else {
172 Some(content_inspector::inspect(&first_line[..]))
173 };
175 if content_type == Some(ContentType::UTF_16LE) {
176 reader.read_until(0x00, &mut first_line).ok();
177 }
179 InputReader {
180 inner: Box::new(reader),
181 first_line,
182 content_type,
183 }
184 }
186 pub(crate) fn read_line(&mut self, buf: &mut Vec<u8>) -> io::Result<bool> {
187 if self.first_line.is_empty() {
188 let res = self.inner.read_until(b'\n', buf).map(|size| size > 0)?;
190 if self.content_type == Some(ContentType::UTF_16LE) {
191 self.inner.read_until(0x00, buf).ok();
192 }
194 Ok(res)
195 } else {
196 buf.append(&mut self.first_line);
197 Ok(true)
198 }
199 }
202#[test]
203fn basic() {
204 let content = b"#!/bin/bash\necho hello";
205 let mut reader = InputReader::new(&content[..]);
207 assert_eq!(b"#!/bin/bash\n", &reader.first_line[..]);
209 let mut buffer = vec![];
211 let res = reader.read_line(&mut buffer);
212 assert!(res.is_ok());
213 assert_eq!(true, res.unwrap());
214 assert_eq!(b"#!/bin/bash\n", &buffer[..]);
216 buffer.clear();
218 let res = reader.read_line(&mut buffer);
219 assert!(res.is_ok());
220 assert_eq!(true, res.unwrap());
221 assert_eq!(b"echo hello", &buffer[..]);
223 buffer.clear();
225 let res = reader.read_line(&mut buffer);
226 assert!(res.is_ok());
227 assert_eq!(false, res.unwrap());
228 assert!(buffer.is_empty());
231#[test]
232fn utf16le() {
233 let content = b"\xFF\xFE\x73\x00\x0A\x00\x64\x00";
234 let mut reader = InputReader::new(&content[..]);
236 assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &reader.first_line[..]);
238 let mut buffer = vec![];
240 let res = reader.read_line(&mut buffer);
241 assert!(res.is_ok());
242 assert_eq!(true, res.unwrap());
243 assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &buffer[..]);
245 buffer.clear();
247 let res = reader.read_line(&mut buffer);
248 assert!(res.is_ok());
249 assert_eq!(true, res.unwrap());
250 assert_eq!(b"\x64\x00", &buffer[..]);
252 buffer.clear();
254 let res = reader.read_line(&mut buffer);
255 assert!(res.is_ok());
256 assert_eq!(false, res.unwrap());
257 assert!(buffer.is_empty());