on v0.15.2 help sign in
bat/src/input.rs
267 lines 7.7 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,
42impl OpenedInputKind {
43 pub(crate) fn is_theme_preview_file(&self) -> bool {
44 match self {
45 OpenedInputKind::ThemePreviewFile => true,
46 _ => false,
47 }
48 }
51pub(crate) struct OpenedInput<'a> {
52 pub(crate) kind: OpenedInputKind,
53 pub(crate) metadata: InputMetadata,
54 pub(crate) reader: InputReader<'a>,
57impl<'a> Input<'a> {
58 pub fn ordinary_file(path: &OsStr) -> Self {
59 Input {
60 kind: InputKind::OrdinaryFile(path.to_os_string()),
61 metadata: InputMetadata::default(),
62 }
63 }
65 pub fn stdin() -> Self {
66 Input {
67 kind: InputKind::StdIn,
68 metadata: InputMetadata::default(),
69 }
70 }
72 pub fn theme_preview_file() -> Self {
73 Input {
74 kind: InputKind::ThemePreviewFile,
75 metadata: InputMetadata::default(),
76 }
77 }
79 pub fn from_reader(reader: Box<dyn Read + 'a>) -> Self {
80 Input {
81 kind: InputKind::CustomReader(reader),
82 metadata: InputMetadata::default(),
83 }
84 }
86 pub fn is_stdin(&self) -> bool {
87 if let InputKind::StdIn = self.kind {
88 true
89 } else {
90 false
91 }
92 }
94 pub fn with_name(mut self, provided_name: Option<&OsStr>) -> Self {
95 self.metadata.user_provided_name = provided_name.map(|n| n.to_owned());
96 self
97 }
99 pub(crate) fn open<R: BufRead + 'a>(self, stdin: R) -> Result<OpenedInput<'a>> {
100 match self.kind {
101 InputKind::StdIn => Ok(OpenedInput {
102 kind: OpenedInputKind::StdIn,
103 metadata: self.metadata,
104 reader: InputReader::new(stdin),
105 }),
106 InputKind::OrdinaryFile(path) => Ok(OpenedInput {
107 kind: OpenedInputKind::OrdinaryFile(path.clone()),
108 metadata: self.metadata,
109 reader: {
110 let file = File::open(&path)
111 .map_err(|e| format!("'{}': {}", path.to_string_lossy(), e))?;
112 if file.metadata()?.is_dir() {
113 return Err(format!("'{}' is a directory.", path.to_string_lossy()).into());
114 }
115 InputReader::new(BufReader::new(file))
116 },
117 }),
118 InputKind::ThemePreviewFile => Ok(OpenedInput {
119 kind: OpenedInputKind::ThemePreviewFile,
120 metadata: self.metadata,
121 reader: InputReader::new(THEME_PREVIEW_FILE),
122 }),
123 InputKind::CustomReader(reader) => Ok(OpenedInput {
124 kind: OpenedInputKind::CustomReader,
125 metadata: self.metadata,
126 reader: InputReader::new(BufReader::new(reader)),
127 }),
128 }
129 }
132impl<'a> OpenedInput<'a> {
133 pub fn description(&self) -> InputDescription {
134 if let Some(ref name) = self.metadata.user_provided_name {
135 InputDescription {
136 full: format!("file '{}'", name.to_string_lossy()),
137 prefix: "File: ".to_owned(),
138 name: name.to_string_lossy().into_owned(),
139 }
140 } else {
141 match self.kind {
142 OpenedInputKind::OrdinaryFile(ref path) => InputDescription {
143 full: format!("file '{}'", path.to_string_lossy()),
144 prefix: "File: ".to_owned(),
145 name: path.to_string_lossy().into_owned(),
146 },
147 OpenedInputKind::StdIn => InputDescription {
148 full: "STDIN".to_owned(),
149 prefix: "".to_owned(),
150 name: "STDIN".to_owned(),
151 },
152 OpenedInputKind::ThemePreviewFile => InputDescription {
153 full: "".to_owned(),
154 prefix: "".to_owned(),
155 name: "".to_owned(),
156 },
157 OpenedInputKind::CustomReader => InputDescription {
158 full: "reader".to_owned(),
159 prefix: "".to_owned(),
160 name: "READER".into(),
161 },
162 }
163 }
164 }
167pub(crate) struct InputReader<'a> {
168 inner: Box<dyn BufRead + 'a>,
169 pub(crate) first_line: Vec<u8>,
170 pub(crate) content_type: Option<ContentType>,
173impl<'a> InputReader<'a> {
174 fn new<R: BufRead + 'a>(mut reader: R) -> InputReader<'a> {
175 let mut first_line = vec![];
176 reader.read_until(b'\n', &mut first_line).ok();
178 let content_type = if first_line.is_empty() {
179 None
180 } else {
181 Some(content_inspector::inspect(&first_line[..]))
182 };
184 if content_type == Some(ContentType::UTF_16LE) {
185 reader.read_until(0x00, &mut first_line).ok();
186 }
188 InputReader {
189 inner: Box::new(reader),
190 first_line,
191 content_type,
192 }
193 }
195 pub(crate) fn read_line(&mut self, buf: &mut Vec<u8>) -> io::Result<bool> {
196 if self.first_line.is_empty() {
197 let res = self.inner.read_until(b'\n', buf).map(|size| size > 0)?;
199 if self.content_type == Some(ContentType::UTF_16LE) {
200 self.inner.read_until(0x00, buf).ok();
201 }
203 Ok(res)
204 } else {
205 buf.append(&mut self.first_line);
206 Ok(true)
207 }
208 }
211#[test]
212fn basic() {
213 let content = b"#!/bin/bash\necho hello";
214 let mut reader = InputReader::new(&content[..]);
216 assert_eq!(b"#!/bin/bash\n", &reader.first_line[..]);
218 let mut buffer = vec![];
220 let res = reader.read_line(&mut buffer);
221 assert!(res.is_ok());
222 assert_eq!(true, res.unwrap());
223 assert_eq!(b"#!/bin/bash\n", &buffer[..]);
225 buffer.clear();
227 let res = reader.read_line(&mut buffer);
228 assert!(res.is_ok());
229 assert_eq!(true, res.unwrap());
230 assert_eq!(b"echo hello", &buffer[..]);
232 buffer.clear();
234 let res = reader.read_line(&mut buffer);
235 assert!(res.is_ok());
236 assert_eq!(false, res.unwrap());
237 assert!(buffer.is_empty());
240#[test]
241fn utf16le() {
242 let content = b"\xFF\xFE\x73\x00\x0A\x00\x64\x00";
243 let mut reader = InputReader::new(&content[..]);
245 assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &reader.first_line[..]);
247 let mut buffer = vec![];
249 let res = reader.read_line(&mut buffer);
250 assert!(res.is_ok());
251 assert_eq!(true, res.unwrap());
252 assert_eq!(b"\xFF\xFE\x73\x00\x0A\x00", &buffer[..]);
254 buffer.clear();
256 let res = reader.read_line(&mut buffer);
257 assert!(res.is_ok());
258 assert_eq!(true, res.unwrap());
259 assert_eq!(b"\x64\x00", &buffer[..]);
261 buffer.clear();
263 let res = reader.read_line(&mut buffer);
264 assert!(res.is_ok());
265 assert_eq!(false, res.unwrap());
266 assert!(buffer.is_empty());