use std::env::args_os;
use std::iter::ExactSizeIterator;
use std::io::{self, Read};
use std::io::{BufReader, BufRead};
use std::fs::File;
use std::path::Path;
use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::convert::From;
#[derive(Debug)]
pub struct FailReadFileError {
pub inner: io::Error,
pub filename: String
}
impl Display for FailReadFileError {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
writeln!(f, "could not read file {}", self.filename)?;
write!(f, "caused by: {}", self.inner)?;
Ok(())
}
}
impl Error for FailReadFileError {
fn description(&self) -> &str {
"failed to read file"
}
fn cause(&self) -> Option<&Error> {
Some(&self.inner)
}
}
#[derive(Debug)]
pub struct InputError {
pub badfiles: Vec<FailReadFileError>
}
impl Display for InputError {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
for e in &self.badfiles {
writeln!(f, "{}", e)?;
}
Ok(())
}
}
impl Error for InputError {
fn description(&self) -> &str {
"failed to read one or more files"
}
fn cause(&self) -> Option<&Error> {
let first = self.badfiles.first();
match first {
Some(err) => Some(err),
None => None
}
}
}
impl From<Vec<FailReadFileError>> for InputError {
fn from(err: Vec<FailReadFileError>) -> Self {
InputError { badfiles: err }
}
}
trait TryIterator {
type Item;
type JIter: ExactSizeIterator<Item=Self::Item>;
fn attempt_map<F, T, E>(self, mapper: F) -> Result<Vec<T>, Vec<E>> where
F: Fn(Self::Item) -> Result<T, E>;
}
impl<I> TryIterator for I where
I: ExactSizeIterator
{
type Item = I::Item;
type JIter = I;
fn attempt_map<F, T, E>(self, mapper: F) -> Result<Vec<T>, Vec<E>> where
F: Fn(Self::Item) -> Result<T, E>
{
let mut any_failure = false;
let mut successes = Vec::new();
let mut failures = Vec::new();
for obj in self {
match mapper(obj) {
Ok(output) => {
if !any_failure {
successes.push(output);
}
},
Err(err) => {
any_failure = true;
failures.push(err);
}
};
}
if any_failure { Err(failures) } else { Ok(successes) }
}
}
pub type Lines = io::Lines<BufReader<Box<Read>>>;
pub fn argf_lines() -> Result<Lines, InputError> {
let chained = argf()?;
let buffered = BufReader::new(chained);
Ok(buffered.lines())
}
pub fn argf() -> Result<Box<Read>, InputError> {
let args = args_os().skip(1);
input(args)
}
pub fn input_lines<I, J, S>(inputs: I) -> Result<Lines, InputError> where
I: IntoIterator<Item=S, IntoIter=J>,
J: ExactSizeIterator<Item=S>,
S: AsRef<Path>
{
let chained = input(inputs)?;
let buffered = BufReader::new(chained);
Ok(buffered.lines())
}
pub fn input<I, J, S>(inputs: I) -> Result<Box<Read>, InputError> where
I: IntoIterator<Item=S, IntoIter=J>,
J: ExactSizeIterator<Item=S>,
S: AsRef<Path>
{
let iter = inputs.into_iter();
if iter.len() == 0 {
Ok(Box::new(io::stdin()))
} else {
let reads = iter.attempt_map(|path| from_arg(path.as_ref()))?;
Ok(chain_all_reads(reads))
}
}
fn chain_all_reads<I>(reads: I) -> Box<Read> where
I: IntoIterator<Item=Box<Read>>
{
reads.into_iter().fold(Box::new(io::empty()), |read, next| {
Box::new(read.chain(next))
})
}
fn from_arg<'a>(arg: &'a Path) -> Result<Box<Read>, FailReadFileError> {
let str_repr = arg.to_string_lossy();
if str_repr == "-" {
Ok(Box::new(io::stdin()))
} else {
let file = File::open(arg).map_err(|err| {
FailReadFileError {
inner: err,
filename: arg.to_string_lossy().to_string()
}
})?;
Ok(Box::new(file))
}
}