1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use std::fs::File;
use std::io::{
    BufRead, BufReader as IoBufReader, Error as IoError, ErrorKind as IoErrorKind,
    Result as IoResult,
};
use std::path::Path;
use std::rc::Rc;
use std::str;

/// Default buffer size of the reader.
const DEFAULT_BUFFER_SIZE: usize = 1024;

/// Buffered reader.
pub struct BufReader {
    /// Inner type.
    reader: IoBufReader<File>,
    /// Buffer.
    buffer: Rc<Vec<u8>>,
}

impl BufReader {
    /// Opens the given file and initializes the buffered reader with given buffer size.
    pub fn open<P: AsRef<Path>>(path: P, buffer_size: Option<usize>) -> IoResult<Self> {
        let file = File::open(path)?;
        let reader = IoBufReader::new(file);
        let buffer = Self::new_buffer(buffer_size);
        Ok(Self { reader, buffer })
    }

    /// Creates a new buffer with the given size.
    fn new_buffer(buffer_size: Option<usize>) -> Rc<Vec<u8>> {
        Rc::new(Vec::with_capacity(
            buffer_size.unwrap_or(DEFAULT_BUFFER_SIZE),
        ))
    }
}

impl Iterator for BufReader {
    type Item = IoResult<Rc<Vec<u8>>>;
    fn next(&mut self) -> Option<Self::Item> {
        let buffer = match Rc::get_mut(&mut self.buffer) {
            Some(rc_buffer) => {
                rc_buffer.clear();
                rc_buffer
            }
            None => {
                self.buffer = Self::new_buffer(None);
                Rc::make_mut(&mut self.buffer)
            }
        };
        self.reader
            .read_until(b'\n', buffer)
            .map(|u| {
                if u == 0 {
                    None
                } else {
                    Some(Rc::clone(&self.buffer))
                }
            })
            .transpose()
    }
}

/// Reads the contents of the file into a string.
///
/// Uses [`BufReader`] under the hood.
pub fn read_to_string<P: AsRef<Path>>(path: P) -> IoResult<String> {
    let mut lines = Vec::<String>::new();
    for line in BufReader::open(path, None)? {
        lines.push(
            str::from_utf8(&line?)
                .map_err(|e| IoError::new(IoErrorKind::Other, e))?
                .to_string(),
        );
    }
    Ok(lines.join(""))
}

/// Reads (decodes) the given gzip file into a string.
///
/// Uses [`BufReader`] under the hood.
#[cfg(feature = "gzip")]
pub fn read_gzip<P: AsRef<Path>>(path: P) -> IoResult<String> {
    use std::io::Read;
    let mut bytes = Vec::<u8>::new();
    for read_bytes in BufReader::open(path, None)? {
        bytes.extend(read_bytes?.to_vec());
    }
    let mut gz = flate2::read::GzDecoder::new(&bytes[..]);
    let mut data = String::new();
    gz.read_to_string(&mut data)?;
    Ok(data)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::error::Error;
    use std::path::PathBuf;

    #[test]
    fn test_file_reader() -> Result<(), Error> {
        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml");
        assert!(read_to_string(path)?.contains(&format!("name = \"{}\"", env!("CARGO_PKG_NAME"))));
        Ok(())
    }

    #[cfg(feature = "gzip")]
    #[test]
    fn test_gzip_reader() -> Result<(), Error> {
        use flate2::write::GzEncoder;
        use flate2::Compression;
        use std::fs::{self, File};
        use std::io::Write;

        let data = "gzip-test";
        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test.gz");
        let file = File::create(&path)?;
        let mut encoder = GzEncoder::new(file, Compression::default());
        encoder.write_all(data.as_bytes())?;
        encoder.finish()?;

        assert_eq!(data, read_gzip(&path)?);
        fs::remove_file(path)?;
        Ok(())
    }
}