#[cfg(feature = "tokio")]
use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncWrite};
#[cfg(feature = "bgzip")]
use crate::bgzip::BgzipCompress;
#[cfg(feature = "bzip2")]
use crate::bzip2::{Bzip2Compress, Bzip2Decompress};
#[cfg(feature = "flate2")]
use crate::gzip::{GzipCompress, GzipDecompress};
#[cfg(feature = "xz")]
use crate::xz::{XzCompress, XzDecompress};
#[cfg(feature = "zstd")]
use crate::zstd::{ZstdCompress, ZstdDecompress};
#[cfg(feature = "rayon")]
use crate::io::ParallelCompressWriter;
#[cfg(feature = "tokio")]
use crate::{io::AsyncProcessorReader, io::AsyncProcessorWriter};
use crate::{
io::ProcessorReader, io::ProcessorWriter, CompressionLevel, PlainProcessor, Processor,
};
use std::{
fs::File,
io::{BufRead, BufReader, Read, Result, Write},
path::Path,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileFormat {
Plain,
#[cfg(feature = "flate2")]
Gzip,
#[cfg(feature = "bgzip")]
BGZip,
#[cfg(feature = "bzip2")]
Bzip2,
#[cfg(feature = "xz")]
Xz,
#[cfg(feature = "zstd")]
Zstd,
}
impl FileFormat {
pub fn extension(self) -> &'static str {
match self {
Self::Plain => "",
#[cfg(feature = "flate2")]
Self::Gzip => "gz",
#[cfg(feature = "bgzip")]
Self::BGZip => "bgz",
#[cfg(feature = "bzip2")]
Self::Bzip2 => "bz2",
#[cfg(feature = "xz")]
Self::Xz => "xz",
#[cfg(feature = "zstd")]
Self::Zstd => "zst",
}
}
pub fn from_path<P: AsRef<Path>>(path: P) -> Option<Self> {
let ext = path.as_ref().extension().and_then(|s| s.to_str());
match ext {
#[cfg(feature = "flate2")]
Some("gz") => Some(Self::Gzip),
#[cfg(feature = "bgzip")]
Some("bgz") => Some(Self::BGZip),
#[cfg(feature = "bzip2")]
Some("bz2") => Some(Self::Bzip2),
#[cfg(feature = "xz")]
Some("xz") => Some(Self::Xz),
#[cfg(feature = "zstd")]
Some("zst") => Some(Self::Zstd),
_ => None,
}
}
pub fn from_buf(buf: &[u8]) -> Option<Self> {
if buf.starts_with(&[0x1f, 0x8b]) {
#[cfg(feature = "flate2")]
return Some(Self::Gzip);
}
if buf.starts_with(b"BZ") {
#[cfg(feature = "bzip2")]
return Some(Self::Bzip2);
}
if buf.starts_with(&[0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00]) {
#[cfg(feature = "xz")]
return Some(Self::Xz);
}
if buf.starts_with(&[0x28, 0xB5, 0x2F, 0xFD]) {
#[cfg(feature = "zstd")]
return Some(Self::Zstd);
}
None
}
pub fn from_buf_reader<R: BufRead>(mut reader: R) -> Result<Option<Self>> {
let signature = reader.fill_buf()?;
Ok(Self::from_buf(signature))
}
#[cfg(feature = "tokio")]
pub async fn from_async_buf_reader<R: AsyncBufRead + Unpin>(
mut reader: R,
) -> Result<Option<Self>> {
let signature = reader.fill_buf().await?;
Ok(Self::from_buf(signature))
}
pub fn compressor(
self,
compression_level: CompressionLevel,
) -> Box<dyn Processor + Unpin + Send> {
match self {
Self::Plain => Box::new(PlainProcessor::new()),
#[cfg(feature = "flate2")]
Self::Gzip => Box::new(GzipCompress::new(compression_level.flate2())),
#[cfg(feature = "bgzip")]
Self::BGZip => Box::new(BgzipCompress::new(compression_level.bgzip())),
#[cfg(feature = "bzip2")]
Self::Bzip2 => Box::new(Bzip2Compress::new(compression_level.bzip2())),
#[cfg(feature = "xz")]
Self::Xz => Box::new(XzCompress::new(compression_level.xz()).unwrap()),
#[cfg(feature = "zstd")]
Self::Zstd => Box::new(ZstdCompress::new(compression_level.zstd()).unwrap()),
}
}
pub fn decompressor(self) -> Box<dyn Processor + Unpin + Send> {
match self {
Self::Plain => Box::new(PlainProcessor::new()),
#[cfg(feature = "flate2")]
Self::Gzip => Box::new(GzipDecompress::new()),
#[cfg(feature = "bgzip")]
Self::BGZip => Box::new(GzipDecompress::new()),
#[cfg(feature = "bzip2")]
Self::Bzip2 => Box::new(Bzip2Decompress::new()),
#[cfg(feature = "xz")]
Self::Xz => Box::new(XzDecompress::new(10_000_000).unwrap()),
#[cfg(feature = "zstd")]
Self::Zstd => Box::new(ZstdDecompress::new().unwrap()),
}
}
}
pub fn autodetect_reader<R: Read>(
reader: R,
) -> Result<ProcessorReader<Box<dyn Processor + Send + Unpin>, std::io::BufReader<R>>> {
autodetect_buf_reader(BufReader::new(reader))
}
pub fn autodetect_buf_reader<R: BufRead>(
mut reader: R,
) -> Result<ProcessorReader<Box<dyn Processor + Send + Unpin>, R>> {
let decompressor = if let Some(format) = FileFormat::from_buf_reader(&mut reader)? {
format.decompressor()
} else {
Box::new(PlainProcessor::new())
};
Ok(ProcessorReader::with_processor(decompressor, reader))
}
pub fn autodetect_open<P: AsRef<std::path::Path>>(
path: P,
) -> Result<ProcessorReader<Box<dyn Processor + Send + Unpin>, std::io::BufReader<std::fs::File>>> {
let file = std::fs::File::open(path)?;
autodetect_buf_reader(std::io::BufReader::new(file))
}
pub fn autodetect_open_or_stdin<P: AsRef<Path>>(
path: Option<P>,
) -> Result<impl Read + Send + Unpin> {
let file: Box<dyn BufRead + Send + Unpin> = if let Some(path) = path.as_ref() {
Box::new(std::io::BufReader::new(std::fs::File::open(path)?))
} else {
Box::new(std::io::BufReader::new(std::io::stdin()))
};
autodetect_buf_reader(file)
}
#[cfg(feature = "tokio")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "tokio")))]
pub async fn autodetect_async_buf_reader<R: AsyncBufRead + Unpin>(
mut reader: R,
) -> Result<AsyncProcessorReader<Box<dyn Processor + Send + Unpin>, R>> {
let decompressor = if let Some(format) = FileFormat::from_async_buf_reader(&mut reader).await? {
format.decompressor()
} else {
Box::new(PlainProcessor::new())
};
Ok(AsyncProcessorReader::with_processor(decompressor, reader))
}
#[cfg(feature = "tokio_fs")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "tokio_fs")))]
pub async fn autodetect_async_open<P: AsRef<std::path::Path>>(
path: P,
) -> Result<
AsyncProcessorReader<Box<dyn Processor + Send + Unpin>, tokio::io::BufReader<tokio::fs::File>>,
> {
let file = tokio::fs::File::open(path).await?;
autodetect_async_buf_reader(tokio::io::BufReader::new(file)).await
}
#[cfg(feature = "tokio_fs")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "tokio_fs")))]
pub async fn autodetect_async_open_or_stdin<P: AsRef<Path>>(
path: Option<P>,
) -> Result<impl AsyncRead> {
use std::pin::Pin;
let file: Pin<Box<dyn AsyncBufRead>> = if let Some(path) = path.as_ref() {
Box::pin(tokio::io::BufReader::new(
tokio::fs::File::open(path).await?,
))
} else {
Box::pin(tokio::io::BufReader::new(tokio::io::stdin()))
};
autodetect_async_buf_reader(tokio::io::BufReader::new(file)).await
}
pub fn autodetect_create<P: AsRef<Path>>(
path: P,
compression_level: CompressionLevel,
) -> Result<ProcessorWriter<Box<dyn Processor + Send + Unpin>, File>> {
let compressor = if let Some(format) = FileFormat::from_path(path.as_ref()) {
format.compressor(compression_level)
} else {
Box::new(PlainProcessor::new())
};
Ok(ProcessorWriter::with_processor(
compressor,
File::create(path)?,
))
}
#[cfg(feature = "bgzip")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "bgzip")))]
pub fn autodetect_create_prefer_bgzip<P: AsRef<Path>>(
path: P,
compression_level: CompressionLevel,
) -> Result<ProcessorWriter<Box<dyn Processor + Send + Unpin>, File>> {
let compressor = if let Some(format) = FileFormat::from_path(path.as_ref()) {
match format {
FileFormat::Gzip => Box::new(BgzipCompress::new(compression_level.bgzip())),
_ => format.compressor(compression_level),
}
} else {
Box::new(BgzipCompress::new(compression_level.bgzip()))
};
Ok(ProcessorWriter::with_processor(
compressor,
File::create(path)?,
))
}
pub fn autodetect_create_or_stdout<P: AsRef<Path>>(
path: Option<P>,
compression_level: CompressionLevel,
) -> Result<ProcessorWriter<Box<dyn Processor + Send + Unpin>, Box<dyn Write + Send + Unpin>>> {
let compressor = if let Some(path) = path.as_ref() {
if let Some(format) = FileFormat::from_path(path.as_ref()) {
format.compressor(compression_level)
} else {
Box::new(PlainProcessor::new())
}
} else {
Box::new(PlainProcessor::new())
};
let output: Box<dyn Write + Send + Unpin> = if let Some(path) = path {
Box::new(File::create(path)?)
} else {
Box::new(std::io::stdout())
};
Ok(ProcessorWriter::with_processor(compressor, output))
}
#[cfg(feature = "bgzip")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "bgzip")))]
pub fn autodetect_create_or_stdout_prefer_bgzip<P: AsRef<Path>>(
path: Option<P>,
compression_level: CompressionLevel,
) -> Result<ProcessorWriter<Box<dyn Processor + Send + Unpin>, Box<dyn Write + Send + Unpin>>> {
use std::io::IsTerminal;
let compressor: Box<dyn Processor + Send + Unpin> = if let Some(path) = path.as_ref() {
if let Some(format) = FileFormat::from_path(path.as_ref()) {
match format {
FileFormat::Gzip => Box::new(BgzipCompress::new(compression_level.bgzip())),
_ => format.compressor(compression_level),
}
} else {
Box::new(BgzipCompress::new(compression_level.bgzip()))
}
} else {
let c: Box<dyn Processor + Send + Unpin> = if !std::io::stdout().is_terminal() {
Box::new(BgzipCompress::new(compression_level.bgzip()))
} else {
Box::new(PlainProcessor::new())
};
c
};
let output: Box<dyn Write + Send + Unpin> = if let Some(path) = path {
Box::new(File::create(path)?)
} else {
Box::new(std::io::stdout())
};
Ok(ProcessorWriter::with_processor(compressor, output))
}
#[cfg(feature = "tokio_fs")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "tokio_fs")))]
pub async fn autodetect_async_create<P: AsRef<Path>>(
path: P,
compression_level: CompressionLevel,
) -> Result<AsyncProcessorWriter<Box<dyn Processor + Send + Unpin>, tokio::fs::File>> {
let compressor = if let Some(format) = FileFormat::from_path(path.as_ref()) {
format.compressor(compression_level)
} else {
Box::new(PlainProcessor::new())
};
Ok(AsyncProcessorWriter::with_processor(
compressor,
tokio::fs::File::create(path).await?,
))
}
#[cfg(feature = "tokio_fs")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "tokio_fs")))]
pub async fn autodetect_async_create_or_stdout<P: AsRef<Path>>(
path: Option<P>,
compression_level: CompressionLevel,
) -> Result<
AsyncProcessorWriter<Box<dyn Processor + Send + Unpin>, std::pin::Pin<Box<dyn AsyncWrite>>>,
> {
let compressor = if let Some(path) = path.as_ref() {
if let Some(format) = FileFormat::from_path(path.as_ref()) {
format.compressor(compression_level)
} else {
Box::new(PlainProcessor::new())
}
} else {
Box::new(PlainProcessor::new())
};
let output: std::pin::Pin<Box<dyn AsyncWrite>> = if let Some(path) = path {
Box::pin(tokio::fs::File::create(path).await?)
} else {
Box::pin(tokio::io::stdout())
};
Ok(AsyncProcessorWriter::with_processor(compressor, output))
}
#[cfg(feature = "rayon")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "rayon")))]
pub fn autodetect_parallel_create<P: AsRef<Path>>(
path: P,
compression_level: CompressionLevel,
) -> Result<ParallelCompressWriter<File, Box<dyn Processor + Send + Unpin>>> {
let file = File::create(path.as_ref())?;
let format = FileFormat::from_path(path.as_ref()).unwrap_or(FileFormat::Plain);
Ok(ParallelCompressWriter::new(file, move || {
format.compressor(compression_level)
}))
}
#[cfg(feature = "rayon")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "rayon")))]
pub fn autodetect_parallel_create_or_stdout<P: AsRef<Path>>(
path: Option<P>,
compression_level: CompressionLevel,
) -> Result<ParallelCompressWriter<Box<dyn Write + Send>, Box<dyn Processor + Send + Unpin>>> {
let output: Box<dyn Write + Send> = if let Some(path) = path.as_ref() {
Box::new(std::fs::File::create(path.as_ref())?)
} else {
Box::new(std::io::stdout())
};
let format = path
.map(|x| FileFormat::from_path(x).unwrap_or(FileFormat::Plain))
.unwrap_or(FileFormat::Plain);
Ok(ParallelCompressWriter::new(output, move || {
format.compressor(compression_level)
}))
}
#[cfg(test)]
mod test {
use super::*;
use anyhow::Context;
use std::fs::File;
use std::io::BufReader;
#[cfg(feature = "tokio")]
use tokio::io::AsyncReadExt;
const FILE_LIST: &[&'static str] = &[
"testfiles/pg2701.txt",
#[cfg(feature = "flate2")]
"testfiles/pg2701.txt.bgzip.gz",
#[cfg(feature = "bzip2")]
"testfiles/pg2701.txt.bz2",
#[cfg(feature = "flate2")]
"testfiles/pg2701.txt.gz",
#[cfg(feature = "xz")]
"testfiles/pg2701.txt.xz",
#[cfg(feature = "zstd")]
"testfiles/pg2701.txt.zst",
#[cfg(feature = "bzip2")]
"testfiles/pg2701.txt.multistream.bz2",
#[cfg(feature = "flate2")]
"testfiles/pg2701.txt.multistream.gz",
#[cfg(feature = "xz")]
"testfiles/pg2701.txt.multistream.xz",
#[cfg(feature = "zstd")]
"testfiles/pg2701.txt.multistream.zst",
#[cfg(feature = "flate2")]
"testfiles/pg2701.txt.pigz.gz",
#[cfg(feature = "flate2")]
"testfiles/pg2701.txt.pipe.gz",
];
#[test]
fn test_read() -> anyhow::Result<()> {
let mut expected_data = Vec::new();
File::open("testfiles/pg2701.txt")?.read_to_end(&mut expected_data)?;
for one_file in FILE_LIST {
let mut read_data = Vec::new();
autodetect_reader(BufReader::new(File::open(one_file)?))?
.read_to_end(&mut read_data)?;
assert_eq!(read_data.len(), expected_data.len());
assert_eq!(read_data, expected_data);
}
Ok(())
}
#[cfg(feature = "rayon")]
#[test]
fn test_read_rayon() -> anyhow::Result<()> {
use crate::io::RayonReader;
let mut expected_data = Vec::new();
File::open("testfiles/pg2701.txt")?.read_to_end(&mut expected_data)?;
for one_file in FILE_LIST {
let mut read_data = Vec::new();
RayonReader::new(autodetect_reader(BufReader::new(File::open(one_file)?))?)
.read_to_end(&mut read_data)?;
assert_eq!(read_data.len(), expected_data.len());
assert_eq!(read_data, expected_data);
}
Ok(())
}
#[cfg(feature = "tokio")]
#[tokio::test]
async fn test_read_async() -> anyhow::Result<()> {
let mut expected_data = Vec::new();
File::open("testfiles/pg2701.txt")?.read_to_end(&mut expected_data)?;
for one_file in FILE_LIST {
let mut read_data = Vec::new();
autodetect_async_buf_reader(tokio::io::BufReader::new(
tokio::fs::File::open(one_file).await?,
))
.await?
.read_to_end(&mut read_data)
.await?;
assert_eq!(read_data.len(), expected_data.len());
assert_eq!(read_data, expected_data);
}
Ok(())
}
#[test]
fn test_file() -> anyhow::Result<()> {
let mut expected_data = Vec::new();
File::open("testfiles/pg2701.txt")?.read_to_end(&mut expected_data)?;
for one_file in FILE_LIST {
let mut read_data = Vec::new();
autodetect_open(one_file)?.read_to_end(&mut read_data)?;
assert_eq!(read_data.len(), expected_data.len());
assert_eq!(read_data, expected_data);
let mut read_data = Vec::new();
autodetect_open_or_stdin(Some(one_file))?.read_to_end(&mut read_data)?;
assert_eq!(read_data.len(), expected_data.len());
assert_eq!(read_data, expected_data);
}
Ok(())
}
#[cfg(feature = "rayon")]
#[test]
fn test_file_rayon() -> anyhow::Result<()> {
use crate::io::RayonReader;
let mut expected_data = Vec::new();
File::open("testfiles/pg2701.txt")?.read_to_end(&mut expected_data)?;
for one_file in FILE_LIST {
if one_file.ends_with(".xz") {
continue;
}
eprintln!("{:?}", one_file);
let mut read_data = Vec::new();
RayonReader::new(autodetect_open(one_file)?).read_to_end(&mut read_data)?;
assert_eq!(read_data.len(), expected_data.len());
assert_eq!(read_data, expected_data);
let mut read_data = Vec::new();
RayonReader::new(autodetect_open_or_stdin(Some(one_file))?)
.read_to_end(&mut read_data)?;
assert_eq!(read_data.len(), expected_data.len());
assert_eq!(read_data, expected_data);
}
Ok(())
}
#[test]
fn test_file_write() -> anyhow::Result<()> {
let mut expected_data = Vec::new();
File::open("testfiles/pg2701.txt")?.read_to_end(&mut expected_data)?;
for one_format in &[
FileFormat::Plain,
#[cfg(feature = "gzip")]
FileFormat::Gzip,
#[cfg(feature = "bgzip")]
FileFormat::BGZip,
#[cfg(feature = "bzip2")]
FileFormat::Bzip2,
#[cfg(feature = "xz")]
FileFormat::Xz,
#[cfg(feature = "zstd")]
FileFormat::Zstd,
] {
let output_filename =
format!("target/test_autodetect_write.{}", one_format.extension());
let mut writer = autodetect_create(&output_filename, CompressionLevel::Default)?;
writer
.write_all(&expected_data)
.with_context(|| format!("write_all error: {:?}", one_format))?;
writer
.flush()
.with_context(|| format!("flush error: {:?}", one_format))?;
let inner_writer = writer.into_inner_writer();
inner_writer.sync_all()?;
let mut read_data = Vec::new();
let mut reader = ProcessorReader::with_processor(
one_format.decompressor(),
std::io::BufReader::new(
File::open(&output_filename)
.with_context(|| format!("file open error: {:?}", one_format))?,
),
);
reader
.read_to_end(&mut read_data)
.with_context(|| format!("read_to_end error: {:?}", one_format))?;
assert_eq!(read_data.len(), expected_data.len());
assert_eq!(read_data, expected_data);
}
Ok(())
}
#[cfg(feature = "tokio_fs")]
#[tokio::test]
async fn test_file_write_async() -> anyhow::Result<()> {
use tokio::io::AsyncWriteExt;
let mut expected_data = Vec::new();
tokio::fs::File::open("testfiles/pg2701.txt")
.await?
.read_to_end(&mut expected_data)
.await?;
for one_format in &[
FileFormat::Plain,
#[cfg(feature = "gzip")]
FileFormat::Gzip,
#[cfg(feature = "bgzip")]
FileFormat::BGZip,
#[cfg(feature = "bzip2")]
FileFormat::Bzip2,
#[cfg(feature = "xz")]
FileFormat::Xz,
#[cfg(feature = "zstd")]
FileFormat::Zstd,
] {
eprintln!("{:?}", one_format);
let output_filename = format!(
"target/test_autodetect_async_write.{}",
one_format.extension()
);
let mut writer =
autodetect_async_create(&output_filename, CompressionLevel::Default).await?;
writer
.write_all(&expected_data)
.await
.with_context(|| format!("write_all: {:?}", one_format))?;
writer
.flush()
.await
.with_context(|| format!("flush error: {:?}", one_format))?;
writer.into_inner_writer().sync_all().await?;
let mut read_data = Vec::new();
let mut reader = AsyncProcessorReader::with_processor(
one_format.decompressor(),
tokio::io::BufReader::new(
tokio::fs::File::open(&output_filename)
.await
.with_context(|| format!("file open error: {:?}", one_format))?,
),
);
reader
.read_to_end(&mut read_data)
.await
.with_context(|| format!("read_to_end error: {:?}", one_format))?;
assert_eq!(read_data.len(), expected_data.len(), "{:?}", one_format);
assert_eq!(read_data, expected_data, "{:?}", one_format);
}
Ok(())
}
#[cfg(feature = "rayon")]
#[test]
fn test_file_write_rayon() -> anyhow::Result<()> {
use anyhow::Context;
use crate::io::{RayonReader, RayonWriter};
let mut expected_data = Vec::new();
File::open("testfiles/pg2701.txt")?.read_to_end(&mut expected_data)?;
for one_format in &[
#[cfg(feature = "gzip")]
FileFormat::Gzip,
#[cfg(feature = "bgzip")]
FileFormat::BGZip,
#[cfg(feature = "bzip2")]
FileFormat::Bzip2,
#[cfg(feature = "xz")]
FileFormat::Xz,
#[cfg(feature = "zstd")]
FileFormat::Zstd,
] {
let output_filename = format!(
"target/test_autodetect_rayon_write.{}",
one_format.extension()
);
let mut writer = RayonWriter::new(autodetect_create(
output_filename.clone(),
CompressionLevel::Default,
)?);
writer
.write_all(&expected_data)
.with_context(|| format!("write_all error: {:?}", one_format))?;
writer
.flush()
.with_context(|| format!("flush error: {:?}", one_format))?;
std::mem::drop(writer);
std::thread::sleep(std::time::Duration::from_millis(100));
let mut read_data = Vec::new();
let mut reader = RayonReader::new(ProcessorReader::with_processor(
one_format.decompressor(),
std::io::BufReader::new(File::open(&output_filename)?),
));
reader
.read_to_end(&mut read_data)
.with_context(|| format!("read_to_end error: {:?}", one_format))?;
assert_eq!(read_data.len(), expected_data.len());
assert_eq!(read_data, expected_data);
}
Ok(())
}
#[cfg(feature = "tokio_fs")]
#[tokio::test]
async fn test_file_async() -> anyhow::Result<()> {
let expected_data = include_bytes!("../testfiles/pg2701.txt");
for one_file in FILE_LIST {
let mut read_data = Vec::new();
autodetect_async_open(one_file)
.await?
.read_to_end(&mut read_data)
.await?;
assert_eq!(read_data.len(), expected_data.len());
assert_eq!(read_data, expected_data);
let mut read_data = Vec::new();
autodetect_async_open_or_stdin(Some(one_file))
.await?
.read_to_end(&mut read_data)
.await?;
assert_eq!(read_data.len(), expected_data.len());
assert_eq!(read_data, expected_data);
}
Ok(())
}
#[test]
fn test_file_format_from_path() {
#[cfg(feature = "flate2")]
assert_eq!(FileFormat::from_path("test.gz"), Some(FileFormat::Gzip));
#[cfg(feature = "bgzip")]
assert_eq!(FileFormat::from_path("test.bgz"), Some(FileFormat::BGZip));
#[cfg(feature = "xz")]
assert_eq!(FileFormat::from_path("test.xz"), Some(FileFormat::Xz));
#[cfg(feature = "bzip2")]
assert_eq!(FileFormat::from_path("test.bz2"), Some(FileFormat::Bzip2));
#[cfg(feature = "zstd")]
assert_eq!(FileFormat::from_path("test.zst"), Some(FileFormat::Zstd));
assert_eq!(FileFormat::from_path("test.c"), None);
assert_eq!(FileFormat::from_path("test"), None);
}
}