use std::path::PathBuf;
use slash_lang::parser::ast::Arg;
use crate::command::{MethodDef, SlashCommand};
use crate::executor::{CommandOutput, ExecutionError, PipeValue};
pub struct Find;
impl SlashCommand for Find {
fn name(&self) -> &str {
"find"
}
fn methods(&self) -> &[MethodDef] {
static METHODS: [MethodDef; 1] = [MethodDef::with_value("content")];
&METHODS
}
fn execute(
&self,
primary: Option<&str>,
args: &[Arg],
_input: Option<&PipeValue>,
) -> Result<CommandOutput, ExecutionError> {
let pattern = primary.ok_or_else(|| {
ExecutionError::Runner("/find requires a pattern: /find(src/**/*.rs)".into())
})?;
let content_filter = args
.iter()
.find(|a| a.name == "content")
.and_then(|a| a.value.as_deref());
let paths = glob_walk(pattern)?;
let result = if let Some(needle) = content_filter {
let mut matches = Vec::new();
for path in &paths {
if let Ok(content) = std::fs::read_to_string(path) {
for (line_num, line) in content.lines().enumerate() {
if line.contains(needle) {
matches.push(format!("{}:{}:{}", path.display(), line_num + 1, line));
}
}
}
}
matches.join("\n")
} else {
paths
.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join("\n")
};
if result.is_empty() {
return Ok(CommandOutput {
stdout: None,
stderr: None,
success: true,
});
}
let mut out = result;
out.push('\n');
Ok(CommandOutput {
stdout: Some(out.into_bytes()),
stderr: None,
success: true,
})
}
}
fn glob_walk(pattern: &str) -> Result<Vec<PathBuf>, ExecutionError> {
let mut results = Vec::new();
let parts: Vec<&str> = pattern.split('/').collect();
walk_recursive(&PathBuf::from("."), &parts, 0, &mut results);
results.sort();
Ok(results)
}
fn walk_recursive(dir: &PathBuf, parts: &[&str], depth: usize, results: &mut Vec<PathBuf>) {
if depth >= parts.len() {
return;
}
let part = parts[depth];
let is_last = depth == parts.len() - 1;
if part == "**" {
walk_recursive(dir, parts, depth + 1, results);
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
walk_recursive(&path, parts, depth, results);
}
}
}
} else {
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let name = entry.file_name();
let name_str = name.to_string_lossy();
if glob_matches(part, &name_str) {
let path = entry.path();
if is_last {
if path.is_file() {
results.push(path);
}
} else if path.is_dir() {
walk_recursive(&path, parts, depth + 1, results);
}
}
}
}
}
}
fn glob_matches(pattern: &str, name: &str) -> bool {
let p: Vec<char> = pattern.chars().collect();
let n: Vec<char> = name.chars().collect();
glob_match_inner(&p, 0, &n, 0)
}
fn glob_match_inner(p: &[char], pi: usize, n: &[char], ni: usize) -> bool {
if pi == p.len() {
return ni == n.len();
}
if p[pi] == '*' {
for skip in 0..=(n.len() - ni) {
if glob_match_inner(p, pi + 1, n, ni + skip) {
return true;
}
}
false
} else if p[pi] == '?' {
ni < n.len() && glob_match_inner(p, pi + 1, n, ni + 1)
} else {
ni < n.len() && p[pi] == n[ni] && glob_match_inner(p, pi + 1, n, ni + 1)
}
}