Files
sausage/userland/findutils/src/find.rs
T
surma b9e3e3677d userland: search and exec tools, spawn library (T14)
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.
2026-11-06 17:21:51 +00:00

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 } }); }