tomq 0.1.2

jq, but from TOML
use std::fs::File;
use std::io::{BufReader, Chain, Cursor, Read};
use std::path::PathBuf;

/// Chain multiple readers together and interleaves them with a separator.
///
/// Used for reading multiple files and producing a single stream of documents
/// that looks like a straight multi-document stream.
pub(crate) struct MultiFileRead {
    current: Chain<BufReader<File>, Cursor<Vec<u8>>>,
    previous_path: Option<PathBuf>,
    current_path: PathBuf,
    next: Vec<PathBuf>,
    separator: Vec<u8>,
}

impl MultiFileRead {
    pub(crate) fn new(files: Vec<PathBuf>, separator: String) -> std::io::Result<Self> {
        let separator = separator.as_bytes().to_vec();
        let mut files = files;
        let first = files.remove(0);
        let current = BufReader::new(File::open(&first)?).chain(Cursor::new(separator.clone()));

        Ok(Self {
            current,
            previous_path: None,
            current_path: first,
            next: files,
            separator,
        })
    }
}

impl Read for MultiFileRead {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        let n = self.current.read(buf).map_err(|e| {
            std::io::Error::new(
                e.kind(),
                format!(
                    "failed while reading file {}: {}",
                    self.current_path.display(),
                    e
                ),
            )
        })?;

        if n == 0 && !self.next.is_empty() {
            loop {
                if let Some(p) = self.next.pop() {
                    self.current =
                        BufReader::new(File::open(&p)?).chain(Cursor::new(self.separator.clone()));
                    self.previous_path = Some(std::mem::replace(&mut self.current_path, p));

                    let n = self.current.read(buf).map_err(|e| {
                        std::io::Error::new(
                            e.kind(),
                            format!(
                                "failed while reading file {}: {}",
                                self.current_path.display(),
                                e
                            ),
                        )
                    })?;

                    if n > 0 {
                        return Ok(n);
                    }
                } else {
                    return Ok(0);
                }
            }
        }

        Ok(n)
    }
}