use colored::{Colorize, control};
use fancy_regex::RegexBuilder;
use std::{env, error::Error, fs, path::PathBuf, process};
use grepme::config::{Config, HELPTEXT};
use grepme::{ExpansionFailed, search, targets};
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() == 1 || args[1].eq("--help") || args[1].eq("-h") {
println!("{HELPTEXT}");
process::exit(0);
}
let config: Config = Config::build(&args).unwrap_or_else(|err| {
eprintln!("Problem parsing arguments: {err}");
process::exit(1);
});
if let Err(e) = run(config) {
eprintln!("Problem getting matches: {e}");
process::exit(1);
}
}
fn run(config: Config) -> Result<(), Box<dyn Error>> {
let output_file = config.output_file.map(PathBuf::from);
if output_file.is_some() {
control::set_override(false);
}
let query = build_query(&config.query, config.ignore_case)?;
let (paths, errors) =
targets::expand_to_files(&config.targets, &output_file);
report_expansion_errors(&errors);
let (output, skipped_count) = search::search_valid_files(&paths, &query);
if skipped_count > 0 {
eprintln!(
"{} {} {}",
"Skipped parsing".yellow().bold(),
skipped_count.to_string().yellow().bold(),
"files containing invalid UTF-8".yellow().bold(),
);
}
write_output(&output, output_file)
}
fn build_query(
query: &str,
ignore_case: bool,
) -> Result<fancy_regex::Regex, fancy_regex::Error> {
let mut query_builder = RegexBuilder::new(query);
if ignore_case {
query_builder.case_insensitive(true).build()
} else {
query_builder.build()
}
}
fn report_expansion_errors(errors: &[ExpansionFailed]) {
if errors.is_empty() {
return;
}
eprintln!("{}", "non-blocking issues:".red().bold());
for error in errors {
match error {
ExpansionFailed::IoError((p, e)) => {
eprintln!(
" {} {} - {}",
"io error:".red(),
p.display(),
e.italic()
)
}
ExpansionFailed::FolderNotRecursed(p) => {
eprintln!(
" {} {}",
"could not read folder:".red(),
p.display()
)
}
ExpansionFailed::SymlinkNotFollowed(p) => {
eprintln!(" {} {}", "symlink skipped:".red(), p.display())
}
}
}
}
fn write_output(
output: &str,
output_file: Option<PathBuf>,
) -> Result<(), Box<dyn Error>> {
if output.is_empty() {
eprintln!("No matches found");
return Ok(());
}
match output_file {
Some(path) => fs::write(path, output.trim_end_matches('\n'))?,
None => println!("{}", output.trim_end_matches('\n')),
}
Ok(())
}