use std::path::Path;
pub const MAX_DEPTH: usize = 3;
const MAX_ENTRIES_SCANNED: usize = 4000;
const MAX_LISTED: usize = 10;
const SKIP_DIRS: &[&str] = &["target", "node_modules", "logs", ".git"];
pub fn suggest_workloads(partial: &str) -> Vec<String> {
if partial.is_empty() {
return Vec::new();
}
let needle = leaf_of(partial);
let mut out = Vec::new();
let mut budget = MAX_ENTRIES_SCANNED;
walk(
Path::new("."),
String::new(),
partial,
needle,
0,
&mut budget,
&mut out,
);
out.extend(bundled_matches(partial, needle));
out.sort();
out.dedup();
out
}
pub fn suggest_bundled(partial: &str) -> Vec<String> {
if partial.is_empty() {
return Vec::new();
}
let mut out = bundled_matches(partial, leaf_of(partial));
out.sort();
out.dedup();
out
}
pub fn did_you_mean(hits: &[String]) -> String {
if hits.is_empty() {
return String::new();
}
let shown: Vec<&str> = hits.iter().take(MAX_LISTED).map(String::as_str).collect();
let overflow = hits.len() - shown.len();
let list = shown.join(", ");
if overflow > 0 {
format!(
" Did you mean one of: {list} \
(+{overflow} more — `nmbrs describe workloads --all`)"
)
} else {
format!(" Did you mean: {list}?")
}
}
fn bundled_matches(partial: &str, needle: &str) -> Vec<String> {
crate::catalog::iter()
.map(|w| w.name)
.filter(|n| n.starts_with(partial) || leaf_of(n).starts_with(needle))
.map(str::to_string)
.collect()
}
fn leaf_of(s: &str) -> &str {
s.rsplit('/').next().unwrap_or(s)
}
fn is_catalog_duplicate(rel: &str) -> bool {
catalog_name_for_local(rel).is_some_and(|n| crate::catalog::lookup(&n).is_some())
}
fn catalog_name_for_local(rel: &str) -> Option<String> {
let stem = rel
.strip_suffix(".yaml")
.or_else(|| rel.strip_suffix(".yml"))?;
if let Some(rest) = stem.strip_prefix("examples/workloads/") {
return Some(format!("examples/{rest}"));
}
stem.strip_prefix("workloads/").map(str::to_string)
}
fn walk(
dir: &Path,
rel: String,
partial: &str,
needle: &str,
depth: usize,
budget: &mut usize,
out: &mut Vec<String>,
) {
if *budget == 0 {
return;
}
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
if *budget == 0 {
return;
}
*budget -= 1;
let name = entry.file_name().to_string_lossy().into_owned();
if name.starts_with('.') {
continue;
}
let path = entry.path();
let child_rel = if rel.is_empty() {
name.clone()
} else {
format!("{rel}/{name}")
};
if path.is_dir() {
if SKIP_DIRS.contains(&name.as_str()) {
continue;
}
if depth < MAX_DEPTH {
walk(&path, child_rel, partial, needle, depth + 1, budget, out);
}
continue;
}
let Some(stem) = name
.strip_suffix(".yaml")
.or_else(|| name.strip_suffix(".yml"))
else {
continue;
};
if (child_rel.starts_with(partial) || stem.starts_with(needle))
&& !is_catalog_duplicate(&child_rel)
{
out.push(child_rel);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
fn touch(p: &Path) {
if let Some(parent) = p.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(p, "ops: { a: { raw: x } }\n").unwrap();
}
fn local_hits(root: &Path, partial: &str) -> Vec<String> {
let needle = leaf_of(partial);
let mut out = Vec::new();
let mut budget = MAX_ENTRIES_SCANNED;
walk(
root,
String::new(),
partial,
needle,
0,
&mut budget,
&mut out,
);
out.sort();
out
}
#[test]
fn leaf_match_surfaces_buried_stem() {
let dir = std::env::temp_dir().join(format!("nmbrs-suggest-{}", std::process::id()));
let _ = fs::remove_dir_all(&dir);
touch(&dir.join("examples/workloads/controls/phase_poll_smoke.yaml"));
let hits = local_hits(&dir, "phase_poll");
assert_eq!(
hits,
vec!["examples/workloads/controls/phase_poll_smoke.yaml"]
);
}
#[test]
fn depth_bound_excludes_level_four() {
let dir = std::env::temp_dir().join(format!("nmbrs-suggest-depth-{}", std::process::id()));
let _ = fs::remove_dir_all(&dir);
touch(&dir.join("a/b/c/match_me.yaml"));
touch(&dir.join("a/b/c/d/match_me.yaml"));
let hits = local_hits(&dir, "match_me");
assert_eq!(hits, vec!["a/b/c/match_me.yaml"]);
}
#[test]
fn slash_qualified_partial_matches_by_full_prefix() {
let dir = std::env::temp_dir().join(format!("nmbrs-suggest-slash-{}", std::process::id()));
let _ = fs::remove_dir_all(&dir);
touch(&dir.join("cursors/all_cursor/enumerate.yaml"));
let hits = local_hits(&dir, "cursors/all_cursor/enum");
assert_eq!(hits, vec!["cursors/all_cursor/enumerate.yaml"]);
}
#[test]
fn skip_dirs_are_not_walked() {
let dir = std::env::temp_dir().join(format!("nmbrs-suggest-skip-{}", std::process::id()));
let _ = fs::remove_dir_all(&dir);
touch(&dir.join("target/buried/match_me.yaml"));
touch(&dir.join("kept/match_me.yaml"));
let hits = local_hits(&dir, "match_me");
assert_eq!(hits, vec!["kept/match_me.yaml"]);
}
#[test]
fn local_path_maps_to_catalog_name() {
assert_eq!(
catalog_name_for_local("examples/workloads/controls/phase_poll_smoke.yaml").as_deref(),
Some("examples/controls/phase_poll_smoke")
);
assert_eq!(
catalog_name_for_local("workloads/keyvalue.yaml").as_deref(),
Some("keyvalue")
);
assert_eq!(
catalog_name_for_local("workloads/cql/baselinesv3/keyvalue.yml").as_deref(),
Some("cql/baselinesv3/keyvalue")
);
assert_eq!(catalog_name_for_local("my/own/workload.yaml"), None);
assert_eq!(catalog_name_for_local("examples/notes/readme.txt"), None);
}
#[test]
fn empty_partial_yields_nothing() {
assert!(suggest_workloads("").is_empty());
assert!(suggest_bundled("").is_empty());
}
#[test]
fn did_you_mean_formats_short_and_capped_lists() {
assert_eq!(did_you_mean(&[]), "");
assert_eq!(
did_you_mean(&["a".to_string(), "b".to_string()]),
" Did you mean: a, b?"
);
let many: Vec<String> = (0..MAX_LISTED + 3).map(|i| format!("w{i}")).collect();
let tail = did_you_mean(&many);
assert!(tail.contains("+3 more"), "overflow note: {tail}");
assert!(
tail.contains("Did you mean one of:"),
"capped phrasing: {tail}"
);
}
}