use termcolor::{BufferWriter, ColorChoice};
use trg::{cli::ColorArg, escape, print, search};
pub struct RunStatus {
pub matched: bool,
pub partial: bool,
}
impl RunStatus {
pub fn exit_code(&self) -> i32 {
if self.partial {
2
} else if self.matched {
0
} else {
1
}
}
}
fn main() {
let code = match run() {
Ok(status) => status.exit_code(),
Err(e) => {
eprintln!("trg: {}", escape::for_stderr(&format!("{e:#}")));
RunStatus { matched: false, partial: true }.exit_code()
}
};
std::process::exit(code);
}
fn run() -> anyhow::Result<RunStatus> {
let mut args = trg::cli::parse()?;
for flag in &args.deprecated {
eprintln!("trg: {flag} is deprecated and has no effect: trg always streams since 0.7.0");
}
trg::resource::set_priority(args.nice);
trg::resource::set_io_idle();
let jobs_count = args.jobs;
let gate = trg::resource::load_gate(jobs_count, args.load_limit)
.unwrap_or_else(|| std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(jobs_count)));
let matcher = search::build_matcher(&args.patterns, &args.search)?;
let color = match args.color {
ColorArg::Always => true,
ColorArg::Never => false,
ColorArg::Auto => std::io::IsTerminal::is_terminal(&std::io::stdout()),
};
args.print.color = color;
let escape_for = |tty: bool| match args.escape {
ColorArg::Always => true,
ColorArg::Never => false,
ColorArg::Auto => tty,
};
args.print.escape = escape_for(std::io::IsTerminal::is_terminal(&std::io::stdout()));
escape::set_stderr(escape_for(std::io::IsTerminal::is_terminal(&std::io::stderr())));
let escape_stdout = args.print.escape;
let bw = BufferWriter::stdout(if color { ColorChoice::Always } else { ColorChoice::Never });
let mut matched = false;
let mut partial = false;
let archive_globs = trg::source::build_archive_globs(&args.archive_globs)?;
let (items, path_errors) = trg::source::resolve_with(&args.paths, archive_globs.as_ref());
let globs = trg::source::build_globs(&args.globs)?;
for e in &path_errors {
eprintln!("trg: {}", escape::for_stderr(&e.to_string()));
partial = true;
}
let ctx = search::SearchCtx {
matcher: &matcher,
sep: args.archive_sep,
globs: globs.as_ref(),
max_count: args.search.max_count,
drop_cache: args.drop_cache,
count_mode: args.print.mode == print::Mode::Count,
};
let jobs: Vec<trg::sched::Job> = items
.iter()
.enumerate()
.map(|(i, p)| trg::sched::Job { index: i, path: p.clone() })
.collect();
let written = trg::sched::run_gated(
jobs,
trg::sched::Config { workers: jobs_count, sorted: args.sort, spill_bytes: 64 * 1024 * 1024 },
&gate,
|| bw.buffer(),
|| search::build_searcher(&args.search),
|job, searcher, buf| {
if args.list_members {
let mut out = escape::Escaped::new(buf, escape_stdout);
return search::list_members(&ctx, &job.path, &mut out);
}
let mut printer = print::build(&args.print, buf);
search::search_archive(&ctx, &job.path, searcher, &mut printer)
},
|done| {
if done.outcome.matched {
matched = true;
}
let r = bw.print(&done.buffer);
for n in &done.outcome.notes {
eprintln!("trg: {}", escape::for_stderr(n));
}
for e in &done.outcome.errors {
eprintln!("trg: {}", escape::for_stderr(e));
partial = true;
}
r
},
);
if let Err(e) = written {
if e.kind() == std::io::ErrorKind::BrokenPipe {
} else {
eprintln!("trg: write error: {e}");
partial = true;
}
}
Ok(RunStatus { matched, partial })
}
#[cfg(test)]
mod tests {
use super::RunStatus;
#[test]
fn matches_with_everything_read_is_zero() {
assert_eq!(RunStatus { matched: true, partial: false }.exit_code(), 0);
}
#[test]
fn no_matches_with_everything_read_is_one() {
assert_eq!(RunStatus { matched: false, partial: false }.exit_code(), 1);
}
#[test]
fn anything_unread_is_two_even_with_no_matches() {
assert_eq!(RunStatus { matched: false, partial: true }.exit_code(), 2);
}
#[test]
fn anything_unread_is_two_even_when_matches_were_found() {
assert_eq!(RunStatus { matched: true, partial: true }.exit_code(), 2);
}
}