use clap::Command;
use crate::config::Config;
#[derive(Debug)]
pub struct ResolvedCmd<'a> {
pub cmd: &'a Command,
pub path: String,
}
pub fn walk(root: &Command) -> Vec<ResolvedCmd<'_>> {
let mut out = Vec::new();
let mut stack: Vec<(&Command, Vec<&str>)> = vec![(root, vec![root.get_name()])];
while let Some((cmd, parts)) = stack.pop() {
let path = parts.join(" ");
for sub in cmd.get_subcommands() {
let mut p = parts.clone();
p.push(sub.get_name());
stack.push((sub, p));
}
out.push(ResolvedCmd { cmd, path });
}
out
}
pub fn should_filter(cmd: &Command, path: &str, cfg: &Config) -> bool {
if cmd.is_hide_set() {
return true;
}
if cfg.deprecated_commands.contains(path) {
return true;
}
if is_group_only(cmd) {
return true;
}
let command_name = cfg.command_name.as_deref().unwrap_or("mcp");
let needles = [command_name, "help", "completion"];
if path.split(' ').any(|segment| needles.contains(&segment)) {
return true;
}
false
}
fn is_group_only(cmd: &Command) -> bool {
let requires_sub = cmd.is_subcommand_required_set();
let user_args_count = cmd
.get_arguments()
.filter(|a| {
let id = a.get_id().as_str();
id != "help" && id != "version" && !a.is_global_set()
})
.count();
requires_sub && user_args_count == 0
}
#[cfg(test)]
mod tests {
use clap::Arg;
use super::*;
#[test]
fn walk_includes_root_and_all_descendants() {
let root = Command::new("root")
.subcommand(Command::new("child-a").subcommand(Command::new("grandchild")))
.subcommand(Command::new("child-b"));
let entries = walk(&root);
let paths: Vec<&str> = entries.iter().map(|e| e.path.as_str()).collect();
assert_eq!(entries.len(), 4);
assert!(paths.contains(&"root"));
assert!(paths.contains(&"root child-a"));
assert!(paths.contains(&"root child-a grandchild"));
assert!(paths.contains(&"root child-b"));
}
#[test]
fn walk_path_is_space_joined() {
let root = Command::new("root")
.subcommand(Command::new("child").subcommand(Command::new("grandchild")));
let entries = walk(&root);
let deepest = entries
.iter()
.find(|e| e.path.split(' ').count() == 3)
.expect("grandchild entry present");
assert_eq!(deepest.path, "root child grandchild");
}
#[test]
fn walk_handles_root_with_no_subs() {
let root = Command::new("leaf");
let entries = walk(&root);
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].path, "leaf");
}
#[test]
fn is_group_only_detects_subcommand_required() {
let cmd = Command::new("group")
.subcommand_required(true)
.subcommand(Command::new("noop"));
assert!(is_group_only(&cmd));
}
#[test]
fn is_group_only_false_when_user_args_present() {
let cmd = Command::new("group")
.subcommand_required(true)
.subcommand(Command::new("noop"))
.arg(Arg::new("foo"));
assert!(!is_group_only(&cmd));
}
#[test]
fn is_group_only_false_when_no_subcommand_required() {
let cmd = Command::new("leaf");
assert!(!is_group_only(&cmd));
}
#[test]
fn is_group_only_excludes_globally_marked_args() {
let cmd = Command::new("group")
.subcommand_required(true)
.subcommand(Command::new("noop"))
.arg(Arg::new("verbose").long("verbose").global(true));
assert!(is_group_only(&cmd));
}
#[test]
fn passing_cmd_not_filtered() {
let cmd = Command::new("test");
let cfg = Config::default();
assert!(!should_filter(&cmd, "test", &cfg));
}
#[test]
fn deprecated_cmd_filtered() {
let cmd = Command::new("test");
let cfg = Config::default().deprecate("test");
assert!(should_filter(&cmd, "test", &cfg));
}
#[test]
fn hidden_cmd_filtered() {
let cmd = Command::new("test").hide(true);
let cfg = Config::default();
assert!(should_filter(&cmd, "test", &cfg));
}
#[test]
fn mcp_cmd_filtered_by_segment() {
let cmd = Command::new("mcp");
let cfg = Config::default();
assert!(should_filter(&cmd, "mcp", &cfg));
}
#[test]
fn group_only_cmd_filtered() {
let cmd = Command::new("platform")
.subcommand_required(true)
.subcommand(Command::new("noop"));
let cfg = Config::default();
assert!(should_filter(&cmd, "platform", &cfg));
}
#[test]
fn agent_cmd_filtered_when_command_name_is_agent() {
let cmd = Command::new("agent");
let cfg = Config::default().command_name("agent");
assert!(should_filter(&cmd, "agent", &cfg));
}
#[test]
fn mcp_cmd_passes_when_command_name_is_agent() {
let cmd = Command::new("mcp");
let cfg = Config::default().command_name("agent");
assert!(!should_filter(&cmd, "mcp", &cfg));
}
#[test]
fn normal_cmd_passes_when_command_name_is_agent() {
let cmd = Command::new("status");
let cfg = Config::default().command_name("agent");
assert!(!should_filter(&cmd, "status", &cfg));
}
#[test]
fn segment_filter_skips_helpful_when_no_segment_equals_help() {
let cmd = Command::new("helpful");
let cfg = Config::default();
assert!(
!should_filter(&cmd, "myapp helpful", &cfg),
"segment-equality rule must not filter 'helpful'"
);
}
#[test]
fn segment_filter_skips_completionish_when_no_segment_equals_completion() {
let cmd = Command::new("completionish");
let cfg = Config::default();
assert!(
!should_filter(&cmd, "myapp completionish", &cfg),
"segment-equality rule must not filter 'completionish'"
);
}
#[test]
fn segment_filter_filters_exact_help_segment() {
let cmd = Command::new("help");
let cfg = Config::default();
assert!(
should_filter(&cmd, "myapp help", &cfg),
"segment equal to 'help' must be filtered"
);
}
#[test]
fn segment_filter_filters_exact_completion_segment() {
let cmd = Command::new("completion");
let cfg = Config::default();
assert!(
should_filter(&cmd, "myapp completion", &cfg),
"segment equal to 'completion' must be filtered"
);
}
#[test]
fn segment_filter_catches_mcp_segment_in_middle() {
let cmd = Command::new("install");
let cfg = Config::default();
assert!(
should_filter(&cmd, "foo mcp install", &cfg),
"interior segment equal to 'mcp' must be filtered"
);
}
#[test]
fn segment_filter_allows_make_mcp_root_and_descendants() {
let cmd_root = Command::new("make-mcp");
let cfg = Config::default();
assert!(
!should_filter(&cmd_root, "make-mcp", &cfg),
"make-mcp root must not be filtered by the segment rule"
);
let cmd_leaf = Command::new("build");
assert!(
!should_filter(&cmd_leaf, "make-mcp build", &cfg),
"make-mcp leaf must not be filtered by the segment rule"
);
}
#[test]
fn segment_filter_catches_mcp_segment_inside_make_mcp_tree() {
let cmd = Command::new("mcp");
let cfg = Config::default();
assert!(
should_filter(&cmd, "make-mcp mcp", &cfg),
"make-mcp's nested `mcp` subtree must be filtered"
);
let cmd_leaf = Command::new("tools");
assert!(
should_filter(&cmd_leaf, "make-mcp mcp tools", &cfg),
"make-mcp's `mcp tools` leaf must be filtered"
);
}
#[test]
fn pin_group_only_subcommand_required_no_args_is_filtered() {
let cmd = Command::new("group")
.subcommand_required(true)
.subcommand(Command::new("leaf"));
let cfg = Config::default();
assert!(should_filter(&cmd, "myapp group", &cfg));
}
#[test]
fn pin_leaf_with_user_args_is_not_filtered() {
let cmd = Command::new("leaf").arg(Arg::new("name").long("name"));
let cfg = Config::default();
assert!(!should_filter(&cmd, "myapp leaf", &cfg));
}
#[test]
fn pin_degenerate_leaf_no_args_no_subs_is_not_filtered() {
let cmd = Command::new("ping");
let cfg = Config::default();
assert!(!should_filter(&cmd, "myapp ping", &cfg));
}
}