#[cfg(feature = "http")]
use crate::http::HttpReader;
use crate::path::{ClioPathEnum, InOut};
use crate::{
assert_exists, assert_not_dir, assert_readable, impl_try_from, is_fifo, ClioPath, Error, Result,
};
use is_terminal::IsTerminal;
use std::convert::TryFrom;
use std::ffi::OsStr;
use std::fmt::{self, Debug, Display};
use std::fs::File;
use std::io::{self, BufRead, BufReader, Cursor, Read, Result as IoResult, Seek, Stdin};
#[derive(Debug)]
pub struct Input {
path: ClioPath,
stream: InputStream,
}
#[derive(Debug)]
enum InputStream {
Stdin(Stdin),
Pipe(File),
File(File),
#[cfg(feature = "http")]
#[cfg_attr(docsrs, doc(cfg(feature = "http")))]
Http(HttpReader),
}
impl Input {
pub fn new<S: TryInto<ClioPath>>(path: S) -> Result<Self>
where
crate::Error: From<<S as TryInto<ClioPath>>::Error>,
{
let path = path.try_into()?;
let stream = match &path.path {
ClioPathEnum::Std(_) => InputStream::Stdin(io::stdin()),
ClioPathEnum::Local(file_path) => {
let file = File::open(file_path)?;
if file.metadata()?.is_dir() {
return Err(Error::dir_error());
}
if is_fifo(&file.metadata()?) {
InputStream::Pipe(file)
} else {
InputStream::File(file)
}
}
#[cfg(feature = "http")]
ClioPathEnum::Http(url) => InputStream::Http(HttpReader::new(url.as_str())?),
};
Ok(Input { path, stream })
}
pub fn std() -> Self {
Input {
path: ClioPath::std().with_direction(InOut::In),
stream: InputStream::Stdin(io::stdin()),
}
}
pub fn try_from_os_str(path: &OsStr) -> std::result::Result<Self, std::ffi::OsString> {
TryFrom::try_from(path).map_err(|e: Error| e.to_os_string(path))
}
pub fn len(&self) -> Option<u64> {
match &self.stream {
InputStream::Stdin(_) => None,
InputStream::Pipe(_) => None,
InputStream::File(file) => file.metadata().ok().map(|x| x.len()),
#[cfg(feature = "http")]
InputStream::Http(http) => http.len(),
}
}
pub fn get_file(&mut self) -> Option<&mut File> {
match &mut self.stream {
InputStream::File(file) => Some(file),
_ => None,
}
}
pub fn is_empty(&self) -> Option<bool> {
self.len().map(|l| l == 0)
}
pub fn lock<'a>(&'a mut self) -> Box<dyn BufRead + 'a> {
match &mut self.stream {
InputStream::Stdin(stdin) => Box::new(stdin.lock()),
InputStream::Pipe(pipe) => Box::new(BufReader::new(pipe)),
InputStream::File(file) => Box::new(BufReader::new(file)),
#[cfg(feature = "http")]
InputStream::Http(http) => Box::new(BufReader::new(http)),
}
}
pub fn path(&self) -> &ClioPath {
&self.path
}
pub fn is_std(&self) -> bool {
matches!(self.stream, InputStream::Stdin(_))
}
pub fn is_local(&self) -> bool {
self.path.is_local()
}
pub fn is_tty(&self) -> bool {
self.is_std() && std::io::stdin().is_terminal()
}
pub fn can_seek(&self) -> bool {
matches!(self.stream, InputStream::File(_))
}
}
impl_try_from!(Input);
impl Read for Input {
fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
match &mut self.stream {
InputStream::Stdin(stdin) => stdin.read(buf),
InputStream::Pipe(pipe) => pipe.read(buf),
InputStream::File(file) => file.read(buf),
#[cfg(feature = "http")]
InputStream::Http(reader) => reader.read(buf),
}
}
}
impl Seek for Input {
fn seek(&mut self, pos: io::SeekFrom) -> IoResult<u64> {
match &mut self.stream {
InputStream::Pipe(pipe) => pipe.seek(pos),
InputStream::File(file) => file.seek(pos),
_ => Err(Error::seek_error().into()),
}
}
}
#[derive(Debug, Clone)]
pub struct CachedInput {
path: ClioPath,
data: Cursor<Vec<u8>>,
}
impl CachedInput {
pub fn new<S: TryInto<ClioPath>>(path: S) -> Result<Self>
where
crate::Error: From<<S as TryInto<ClioPath>>::Error>,
{
let mut source = Input::new(path)?;
if source.is_tty() {
return Err(Error::other(
"blocked reading from stdin because it is a tty",
));
}
let capacity = source.len().unwrap_or(4096) as usize;
let mut data = Cursor::new(Vec::with_capacity(capacity));
io::copy(&mut source, &mut data)?;
data.set_position(0);
Ok(CachedInput {
path: source.path,
data,
})
}
pub fn std() -> Result<Self> {
Self::new(ClioPath::std().with_direction(InOut::In))
}
pub fn try_from_os_str(path: &OsStr) -> std::result::Result<Self, std::ffi::OsString> {
TryFrom::try_from(path).map_err(|e: Error| e.to_os_string(path))
}
pub fn len(&self) -> u64 {
self.data.get_ref().len() as u64
}
pub fn is_empty(&self) -> bool {
self.data.get_ref().is_empty()
}
pub fn path(&self) -> &ClioPath {
&self.path
}
pub fn reset(&mut self) {
self.data.set_position(0)
}
pub fn into_vec(self) -> Vec<u8> {
self.data.into_inner()
}
pub fn get_data(&self) -> &[u8] {
self.data.get_ref()
}
}
impl BufRead for CachedInput {
fn fill_buf(&mut self) -> IoResult<&[u8]> {
self.data.fill_buf()
}
fn consume(&mut self, amt: usize) {
self.data.consume(amt)
}
}
impl Read for CachedInput {
fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
self.data.read(buf)
}
}
impl Seek for CachedInput {
fn seek(&mut self, pos: io::SeekFrom) -> IoResult<u64> {
self.data.seek(pos)
}
}
impl_try_from!(CachedInput: Clone - Default);
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct InputPath {
path: ClioPath,
}
impl InputPath {
pub fn new<S: TryInto<ClioPath>>(path: S) -> Result<Self>
where
crate::Error: From<<S as TryInto<ClioPath>>::Error>,
{
let path: ClioPath = path.try_into()?.with_direction(InOut::In);
if path.is_local() {
assert_exists(&path)?;
assert_not_dir(&path)?;
assert_readable(&path)?;
};
Ok(InputPath { path })
}
pub fn std() -> Self {
InputPath {
path: ClioPath::std().with_direction(InOut::In),
}
}
pub fn is_std(&self) -> bool {
self.path.is_std()
}
pub fn is_tty(&self) -> bool {
self.is_std() && std::io::stdin().is_terminal()
}
pub fn is_local(&self) -> bool {
self.path.is_local()
}
pub fn open(self) -> Result<Input> {
self.path.open()
}
pub fn path(&self) -> &ClioPath {
&self.path
}
}
impl_try_from!(InputPath: Clone);