use flate2::read::GzDecoder;
use flate2::write::GzEncoder;
use flate2::Compression;
use std::fs::File;
use std::io::Write;
use std::io::{self, BufWriter};
use std::io::{BufRead, BufReader, Read};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum FileError {
#[error("IO error: {0}")]
IOError(#[from] io::Error),
}
fn is_gzipped_file(file_path: &str) -> io::Result<bool> {
let mut file = File::open(file_path)?;
let mut buffer = [0; 2];
file.read_exact(&mut buffer)?;
Ok(buffer == [0x1f, 0x8b])
}
pub struct InputFile {
pub filepath: String,
pub comments: Option<Vec<String>>,
pub header: Option<String>,
pub skip_lines: usize,
}
impl InputFile {
pub fn new(filepath: &str) -> Self {
Self {
filepath: filepath.to_string(),
comments: None,
header: None,
skip_lines: 0,
}
}
pub fn reader(&self) -> Result<BufReader<Box<dyn Read>>, FileError> {
let file = File::open(self.filepath.clone())?;
let is_gzipped = is_gzipped_file(&self.filepath)?;
let reader: Box<dyn Read> = if is_gzipped {
Box::new(GzDecoder::new(file))
} else {
Box::new(file)
};
Ok(BufReader::new(reader))
}
pub fn collect_metadata(
&mut self,
comment: &str,
header: Option<&str>,
) -> Result<bool, FileError> {
let mut buf_reader = self.reader()?;
let mut comments = Vec::new();
let mut line = String::new();
while buf_reader.read_line(&mut line)? > 0 {
if line.starts_with(comment) {
comments.push(line.trim_end().to_string());
self.skip_lines += 1;
} else if let Some(header_string) = header {
if line.starts_with(header_string) {
self.header = Some(line.trim_end().to_string());
self.skip_lines += 1;
break;
}
break;
}
line.clear();
}
self.comments = Some(comments);
Ok(self.skip_lines > 0)
}
pub fn continue_reading(&self) -> Result<BufReader<Box<dyn Read>>, FileError> {
let mut buf_reader = self.reader()?;
let mut skipped_lines = 0;
let mut line = String::new();
while skipped_lines < self.skip_lines {
buf_reader.read_line(&mut line)?;
skipped_lines += 1;
line.clear();
}
Ok(buf_reader)
}
}
pub struct OutputFile {
pub filepath: String,
pub header: Option<Vec<String>>,
}
impl OutputFile {
pub fn new(filepath: &str, header: Option<Vec<String>>) -> Self {
Self {
filepath: filepath.to_string(),
header,
}
}
pub fn writer(&self) -> Result<Box<dyn Write>, io::Error> {
let outfile = &self.filepath;
let is_gzip = outfile.ends_with(".gz");
let mut writer: Box<dyn Write> = if is_gzip {
Box::new(BufWriter::new(GzEncoder::new(
File::create(outfile)?,
Compression::default(),
)))
} else {
Box::new(BufWriter::new(File::create(outfile)?))
};
if let Some(entries) = &self.header {
for entry in entries {
writeln!(writer, "#{}", entry)?;
}
}
Ok(writer)
}
}