rg (ripgrep 15.2), grep (ours, on ripgrep's regex crates), find and xargs (ours), sed (uutils 0.3 wrapper), diff and cmp (uutils diffutils 0.6 with -r), fd 10.5, jaq 2.3, env and timeout. userland/spawn is a small Command-like client of the process interface. search-test.nu: 42 cases match native tools.
68 lines
3.4 KiB
Rust
68 lines
3.4 KiB
Rust
//! Find world paths by name, type, path, depth, or modification time.
|
|
use std::{io::{self, Write}, path::PathBuf};
|
|
use sausage_spawn::Command;
|
|
|
|
enum Predicate { Name(globset::GlobMatcher), Path(globset::GlobMatcher), Type(String), Newer(std::time::SystemTime) }
|
|
fn run() -> Result<i32, Box<dyn std::error::Error>> {
|
|
let args: Vec<_> = std::env::args().skip(1).collect();
|
|
let mut i = 0;
|
|
let mut roots = Vec::new();
|
|
while i < args.len() && !args[i].starts_with('-') { roots.push(PathBuf::from(&args[i])); i += 1; }
|
|
if roots.is_empty() { roots.push(".".into()); }
|
|
let mut predicates = Vec::new();
|
|
let mut maxdepth = usize::MAX;
|
|
let mut null = false;
|
|
let mut exec = None;
|
|
while i < args.len() {
|
|
let flag = &args[i]; i += 1;
|
|
match flag.as_str() {
|
|
"-print" => (), "-print0" => null = true,
|
|
"-name" | "-path" | "-type" | "-maxdepth" | "-newer" => {
|
|
let value = args.get(i).ok_or("missing predicate argument")?; i += 1;
|
|
match flag.as_str() {
|
|
"-name" => predicates.push(Predicate::Name(globset::Glob::new(value)?.compile_matcher())),
|
|
"-path" => predicates.push(Predicate::Path(globset::Glob::new(value)?.compile_matcher())),
|
|
"-type" => predicates.push(Predicate::Type(value.clone())),
|
|
"-newer" => predicates.push(Predicate::Newer(std::fs::metadata(value)?.modified()?)),
|
|
_ => maxdepth = value.parse()?,
|
|
}
|
|
}
|
|
"-exec" => {
|
|
let start = i;
|
|
while i < args.len() && args[i] != ";" { i += 1; }
|
|
if i == args.len() || i == start { return Err("-exec requires a command and ;".into()); }
|
|
exec = Some(args[start..i].to_vec()); i += 1;
|
|
}
|
|
_ => return Err(format!("unsupported predicate: {flag}").into()),
|
|
}
|
|
}
|
|
let mut status = 0;
|
|
let mut out = io::BufWriter::new(io::stdout().lock());
|
|
for root in roots {
|
|
for entry in walkdir::WalkDir::new(root).max_depth(maxdepth) {
|
|
let entry = match entry { Ok(e) => e, Err(e) => { eprintln!("find: {e}"); status = 1; continue; } };
|
|
let mut matches = true;
|
|
for predicate in &predicates {
|
|
matches &= match predicate {
|
|
Predicate::Name(g) => g.is_match(entry.file_name()),
|
|
Predicate::Path(g) => g.is_match(entry.path()),
|
|
Predicate::Type(t) => match t.as_str() { "f" => entry.file_type().is_file(), "d" => entry.file_type().is_dir(), "l" => entry.file_type().is_symlink(), _ => return Err("unsupported file type".into()) },
|
|
Predicate::Newer(time) => entry.metadata()?.modified()? > *time,
|
|
};
|
|
}
|
|
if !matches { continue; }
|
|
if let Some(argv) = &exec {
|
|
out.flush()?;
|
|
let argv: Vec<_> = argv.iter().map(|arg| arg.replace("{}", &entry.path().to_string_lossy())).collect();
|
|
if let Err(e) = Command::new(&argv[0]).args(&argv[1..]).status() { eprintln!("find: {e}"); status = 1; }
|
|
} else {
|
|
out.write_all(entry.path().as_os_str().as_encoded_bytes())?;
|
|
out.write_all(if null { b"\0" } else { b"\n" })?;
|
|
}
|
|
}
|
|
}
|
|
out.flush()?;
|
|
Ok(status)
|
|
}
|
|
fn main() { std::process::exit(match run() { Ok(code) => code, Err(e) => { eprintln!("find: {e}"); 1 } }); }
|