use strsim::levenshtein;
use crate::types::ColorMode;
pub(crate) const SUBCOMMANDS: &[&str] = &[
"auto",
"agent",
"scan",
"tree",
"slice",
"s", "find",
"f", "findings",
"dead",
"d", "unused",
"cycles",
"c", "trace",
"commands",
"events",
"pipelines",
"insights",
"manifests",
"info",
"lint",
"report",
"help",
"query",
"q", "diff",
"memex",
"crowd",
"tagmap",
"twins",
"t", "suppress",
"routes",
"dist",
"coverage",
"sniff",
"impact",
"i", "focus",
"hotspots",
"layoutmap",
"zombie",
"health",
"h", "audit",
"doctor",
"plan",
"p", "cache",
];
pub fn is_subcommand(arg: &str) -> bool {
SUBCOMMANDS.contains(&arg)
}
pub(super) fn suggest_similar_command(input: &str) -> Option<&'static str> {
let input_lower = input.to_lowercase();
let mut best_match: Option<(&str, usize)> = None;
for &cmd in SUBCOMMANDS {
let distance = levenshtein(&input_lower, cmd);
if distance <= 2 {
if let Some((_, best_dist)) = best_match {
if distance < best_dist {
best_match = Some((cmd, distance));
}
} else {
best_match = Some((cmd, distance));
}
}
}
best_match.map(|(cmd, _)| cmd)
}
pub(super) fn is_jq_filter(arg: &str) -> bool {
let trimmed = arg.trim();
if trimmed.is_empty() {
return false;
}
if trimmed.starts_with('.') || trimmed.starts_with('[') || trimmed.starts_with('{') {
if trimmed.starts_with("./") || trimmed.starts_with(".\\") {
return false;
}
if trimmed.starts_with('.')
&& !trimmed.contains('[')
&& !trimmed.contains('|')
&& std::path::Path::new(trimmed).exists()
{
return false;
}
return true;
}
false
}
pub(super) fn parse_color_mode(value: &str) -> Result<ColorMode, String> {
match value.to_lowercase().as_str() {
"auto" => Ok(ColorMode::Auto),
"always" | "yes" | "true" => Ok(ColorMode::Always),
"never" | "no" | "false" => Ok(ColorMode::Never),
_ => Err(format!(
"Invalid color mode '{}'. Use: auto, always, or never.",
value
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_subcommand() {
assert!(is_subcommand("auto"));
assert!(is_subcommand("scan"));
assert!(is_subcommand("tree"));
assert!(is_subcommand("slice"));
assert!(is_subcommand("dead"));
assert!(is_subcommand("findings"));
assert!(is_subcommand("trace"));
assert!(!is_subcommand("--tree"));
assert!(!is_subcommand("-A"));
assert!(!is_subcommand("unknown"));
}
#[test]
fn test_is_jq_filter() {
assert!(is_jq_filter(".metadata"));
assert!(is_jq_filter(".files[]"));
assert!(is_jq_filter(".files[0]"));
assert!(is_jq_filter("[.files]"));
assert!(is_jq_filter("{foo: .bar}"));
assert!(is_jq_filter(".foo | .bar"));
assert!(!is_jq_filter("./foo"));
assert!(!is_jq_filter(".\\foo"));
assert!(!is_jq_filter("scan"));
assert!(!is_jq_filter("--help"));
assert!(!is_jq_filter(""));
}
#[test]
fn test_parse_color_mode() {
assert!(matches!(parse_color_mode("auto"), Ok(ColorMode::Auto)));
assert!(matches!(parse_color_mode("always"), Ok(ColorMode::Always)));
assert!(matches!(parse_color_mode("yes"), Ok(ColorMode::Always)));
assert!(matches!(parse_color_mode("never"), Ok(ColorMode::Never)));
assert!(matches!(parse_color_mode("no"), Ok(ColorMode::Never)));
assert!(parse_color_mode("invalid").is_err());
}
}