use std::fs::File;
use std::io::{ErrorKind, Read};
use std::path::{Path, PathBuf};
use std::{fmt, fs, io};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Input {
kind: InputKind,
}
impl fmt::Display for Input {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.kind.fmt(f)
}
}
impl Input {
pub fn new(path: &str) -> Self {
let kind = InputKind::File(PathBuf::from(path));
Input { kind }
}
pub fn from_stdin() -> Result<Self, io::Error> {
let mut contents = String::new();
io::stdin().read_to_string(&mut contents)?;
let kind = InputKind::Stdin(contents);
Ok(Input { kind })
}
pub fn kind(&self) -> &InputKind {
&self.kind
}
pub fn read_to_string(&self) -> Result<String, io::Error> {
self.kind.read_to_string()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InputKind {
File(PathBuf),
Stdin(String),
}
impl fmt::Display for InputKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let output = match self {
InputKind::File(file) => file.to_string_lossy().to_string(),
InputKind::Stdin(_) => "-".to_string(),
};
write!(f, "{output}")
}
}
impl From<&Path> for Input {
fn from(value: &Path) -> Self {
let kind = InputKind::File(value.to_path_buf());
Input { kind }
}
}
impl From<PathBuf> for Input {
fn from(value: PathBuf) -> Self {
let kind = InputKind::File(value);
Input { kind }
}
}
impl InputKind {
fn read_to_string(&self) -> Result<String, io::Error> {
match self {
InputKind::File(path) => {
let mut f = File::open(path)?;
let metadata = fs::metadata(path).unwrap();
let mut buffer = vec![0; metadata.len() as usize];
f.read_exact(&mut buffer)?;
string_from_utf8(buffer)
}
InputKind::Stdin(cached) => Ok(cached.clone()),
}
}
}
fn string_from_utf8(buffer: Vec<u8>) -> Result<String, io::Error> {
let mut buffer = buffer;
strip_bom(&mut buffer);
String::from_utf8(buffer).map_err(|e| io::Error::new(ErrorKind::InvalidData, e))
}
fn strip_bom(bytes: &mut Vec<u8>) {
if bytes.starts_with(&[0xefu8, 0xbb, 0xbf]) {
bytes.drain(0..3);
}
}
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn test_strip_bom() {
let mut bytes = vec![];
strip_bom(&mut bytes);
assert!(bytes.is_empty());
let mut bytes = vec![0xef, 0xbb, 0xbf, 0x68, 0x65, 0x6c, 0x6c, 0x6f];
strip_bom(&mut bytes);
assert_eq!(bytes, vec![0x68, 0x65, 0x6c, 0x6c, 0x6f]);
let mut bytes = vec![0x68, 0x65, 0x6c, 0x6c, 0x6f];
strip_bom(&mut bytes);
assert_eq!(bytes, vec![0x68, 0x65, 0x6c, 0x6c, 0x6f]);
}
#[test]
fn test_string_from_utf8_bom() {
let mut bytes = vec![];
strip_bom(&mut bytes);
assert_eq!(string_from_utf8(vec![]).unwrap(), "");
assert_eq!(
string_from_utf8(vec![0xef, 0xbb, 0xbf, 0x68, 0x65, 0x6c, 0x6c, 0x6f]).unwrap(),
"hello"
);
assert_eq!(
string_from_utf8(vec![0x68, 0x65, 0x6c, 0x6c, 0x6f]).unwrap(),
"hello"
);
let err = string_from_utf8(vec![0xef]).err().unwrap();
assert_eq!(
err.to_string(),
"incomplete utf-8 byte sequence from index 0"
);
}
}