use anyhow::anyhow;
use clap::Parser;
use regex::Regex;
use std::fs::File;
use std::io::{BufReader, Read};
use std::path::PathBuf;
use tzgrep::tar_foreach;
enum Pattern {
Regex(Regex),
FixedStrings(String),
}
impl Pattern {
fn new(pattern: String, is_fixed_string: bool) -> Result<Self, regex::Error> {
if is_fixed_string {
Ok(Self::FixedStrings(pattern))
} else {
Ok(Self::Regex(Regex::new(&pattern)?))
}
}
fn is_match(&self, text: &str) -> bool {
match &self {
Self::Regex(x) => x.is_match(text),
Self::FixedStrings(x) => text.contains(x),
}
}
}
fn tar_grep<R: Read>(regex: Pattern, input: R, line_number: bool) -> anyhow::Result<()> {
if line_number {
tar_foreach(input, &mut |filename, line_num, line| {
let line = line.trim_end_matches('\n');
let line = line.trim_end_matches('\r');
if regex.is_match(line) {
println!("{filename}:{line_num}:{line}");
}
})?
} else {
tar_foreach(input, &mut |filename, _, line| {
let line = line.trim_end_matches('\n');
let line = line.trim_end_matches('\r');
if regex.is_match(line) {
println!("{filename}:{line}");
}
})?
}
Ok(())
}
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
pattern: String,
file: Option<PathBuf>,
#[arg(short = 'n', long)]
line_number: bool,
#[arg(short = 'F', long)]
fixed_string: bool,
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let pattern = Pattern::new(cli.pattern, cli.fixed_string)?;
macro_rules! f {
($input:expr) => {
tar_grep(pattern, $input, cli.line_number)
};
}
if let Some(file) = cli.file {
if !file.exists() {
return Err(anyhow!("file NOT exists: {}", file.display()));
}
if file.is_dir() {
return Err(anyhow!("{} is directory", file.display()));
}
let fd = BufReader::new(File::open(&file)?);
match file.extension() {
None => Err(anyhow!("there is no extension")),
Some(ext) if ext == "tar" => f!(fd),
#[cfg(feature = "flate2")]
Some(ext) if ext == "gz" || ext == "tgz" => {
f!(flate2::bufread::GzDecoder::new(fd))
}
#[cfg(feature = "bzip2")]
Some(ext) if ext == "bz2" || ext == "tbz" => {
f!(bzip2::bufread::BzDecoder::new(fd))
}
#[cfg(feature = "lzma-rs")]
Some(ext) if ext == "xz" || ext == "txz" => {
let (reader, mut writer) = os_pipe::pipe()?;
std::thread::scope(|s| {
s.spawn(|| {
let mut fd = fd;
lzma_rs::xz_decompress(&mut fd, &mut writer).unwrap();
});
f!(reader)
})
}
#[cfg(feature = "zstd")]
Some(ext) if ext == "zst" || ext == "tzst" => {
f!(zstd::stream::read::Decoder::new(fd)?)
}
Some(ext) => Err(anyhow!("Unsupported filetype: {ext:?}")),
}
} else {
f!(std::io::stdin().lock())
}
}