use std::{process::ExitCode, sync::Arc};
use clap::{CommandFactory, Parser};
use crate::{
args::{Args, OutputMode},
cache::Cache,
output::Printer,
search::{CachedSearch, Input, Searcher, search},
};
mod args;
mod cache;
mod complete;
mod config;
mod output;
mod queries;
mod search;
fn main() -> ExitCode {
if std::env::var_os("COMPLETE").is_some_and(|shell| shell == "bash")
&& std::env::args_os().len() == 1
{
return match complete::write_bash_registration(&mut std::io::stdout()) {
Ok(()) => ExitCode::SUCCESS,
Err(_) => ExitCode::FAILURE,
};
}
clap_complete::CompleteEnv::with_factory(Args::command).complete();
let argv = match config::with_defaults(std::env::args_os().collect()).and_then(queries::expand)
{
Ok(argv) => argv,
Err(err) => {
eprintln!("topos: {err}");
return ExitCode::from(2);
}
};
let mut args = Args::parse_from(argv);
if args.clear_cache {
return match cache::clear() {
Ok(()) => ExitCode::SUCCESS,
Err(err) => {
eprintln!("topos: could not delete the cache: {err}");
ExitCode::from(2)
}
};
}
if args.list_queries {
return match queries::load() {
Ok(queries) => {
for (name, query) in queries {
let query =
shlex::try_join(query.iter().map(String::as_str)).unwrap_or_default();
output::emit(format!("{name}\t{query}"));
}
ExitCode::SUCCESS
}
Err(err) => {
eprintln!("topos: {err}");
ExitCode::from(2)
}
};
}
let matcher = match args.matcher() {
Ok(matcher) => matcher,
Err(err) => {
eprintln!("topos: {err}");
return ExitCode::from(2);
}
};
if let Some(text) = args.complete.take().or(args.list_books.then(String::new)) {
let json = args.mode == OutputMode::Json;
for suggestion in complete::suggestions(&matcher, &text, &args.format_options()) {
if json {
output::emit(serde_json::to_string(&suggestion).unwrap_or_default());
} else {
output::emit(suggestion.text);
}
}
return ExitCode::SUCCESS;
}
let input = match Input::new(std::mem::take(&mut args.paths), args.text.take()) {
Ok(input) => input,
Err(err) => {
eprintln!("topos: {err}");
return ExitCode::from(2);
}
};
let mut printer = Printer::new(&args, matcher.data().clone());
let (before, after) = args.context_lines();
let cached = args
.cache
.then(|| Cache::open(&args.fingerprint()))
.flatten()
.map(|cache| CachedSearch::new(cache, &matcher));
let walk = match args.walk_options() {
Ok(walk) => walk,
Err(err) => {
eprintln!("topos: {err}");
return ExitCode::from(2);
}
};
let searcher = Arc::new(Searcher {
matcher,
cached: cached.filter(|_| !args.files),
needs_text: before + after > 0 || printer.needs_text(),
walk,
binary: args.search_binary(),
list_only: args.files,
});
let results = search(searcher.clone(), input);
let mut files: Vec<_> = vec![];
let (mut found, mut failed) = (false, false);
for result in results {
match result {
Ok(file) if args.sort => files.push(file),
Ok(file) => found |= printer.file(&file),
Err((path, err)) => {
failed = true;
match path {
Some(path) => eprintln!("topos: {}: {err}", path.display()),
None => eprintln!("topos: {err}"),
}
}
}
}
files.sort_by(|a, b| a.path.cmp(&b.path));
for file in &files {
found |= printer.file(file);
}
printer.finish();
if let Some(cached) = &searcher.cached {
cached.cache.finish();
}
if failed {
ExitCode::from(2)
} else if found {
ExitCode::SUCCESS
} else {
ExitCode::from(1)
}
}