use usage::SpecCommandEffect::{self, Read, Write};
const EFFECTS: &[(&str, SpecCommandEffect)] = &[
("complete-word", Read),
("generate", Read),
("generate completion", Read),
("generate completion-init", Read),
("generate fig", Read),
("generate json", Read),
("generate manpage", Read),
("generate markdown", Read),
("generate sdk", Write),
("lint", Read),
("mcp", Read),
("sponsors", Read),
];
const FLAG_EFFECTS: &[(&str, &str, SpecCommandEffect)] = &[
("generate fig", "out-file", Write),
("generate manpage", "out-file", Write),
("generate markdown", "out-dir", Write),
("generate markdown", "out-file", Write),
];
#[cfg(test)]
const UNCLASSIFIED: &[(&str, &str)] = &[
("bash", "runs a user-supplied script"),
("exec", "runs a user-supplied script"),
("fish", "runs a user-supplied script"),
("powershell", "runs a user-supplied script"),
("zsh", "runs a user-supplied script"),
];
pub(crate) fn apply(spec: &mut usage::Spec) {
for (path, effect) in EFFECTS {
if let Some(cmd) = find_mut(spec, path) {
cmd.effect = Some(*effect);
}
}
for (path, flag_name, effect) in FLAG_EFFECTS {
if let Some(cmd) = find_mut(spec, path) {
if let Some(flag) = cmd.flags.iter_mut().find(|f| f.name == *flag_name) {
flag.effect = Some(*effect);
}
}
}
}
fn find_mut<'a>(spec: &'a mut usage::Spec, path: &str) -> Option<&'a mut usage::SpecCommand> {
let mut cmd = &mut spec.cmd;
for segment in path.split(' ') {
cmd = cmd.subcommands.get_mut(segment)?;
}
Some(cmd)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
fn spec() -> usage::Spec {
let mut cli = <crate::cli::Cli as clap::CommandFactory>::command();
let mut spec = clap_usage::spec(&mut cli, "usage");
apply(&mut spec);
spec
}
fn walk(cmd: &usage::SpecCommand, path: &mut Vec<String>, out: &mut Vec<(String, bool)>) {
for (name, sub) in &cmd.subcommands {
path.push(name.clone());
out.push((path.join(" "), sub.effect.is_some()));
walk(sub, path, out);
path.pop();
}
}
fn commands() -> Vec<(String, bool)> {
let spec = spec();
let mut out = vec![];
walk(&spec.cmd, &mut vec![], &mut out);
out
}
#[test]
fn no_entry_points_at_a_command_that_does_not_exist() {
let real: HashSet<_> = commands().into_iter().map(|(path, _)| path).collect();
let stale: Vec<_> = EFFECTS
.iter()
.map(|(path, _)| *path)
.chain(FLAG_EFFECTS.iter().map(|(path, _, _)| *path))
.chain(UNCLASSIFIED.iter().map(|(path, _)| *path))
.filter(|path| !real.contains(*path))
.collect();
assert!(stale.is_empty(), "no such commands: {stale:?}");
}
#[test]
fn no_flag_entry_points_at_a_flag_that_does_not_exist() {
let spec = spec();
let missing: Vec<_> = FLAG_EFFECTS
.iter()
.filter(|(path, flag_name, _)| {
find(&spec, path).is_none_or(|cmd| !cmd.flags.iter().any(|f| f.name == *flag_name))
})
.map(|(path, flag_name, _)| format!("{path} --{flag_name}"))
.collect();
assert!(missing.is_empty(), "no such flags: {missing:?}");
}
#[test]
fn nothing_is_unclassified_by_accident() {
let deliberate: HashSet<_> = UNCLASSIFIED.iter().map(|(path, _)| *path).collect();
let accidental: Vec<_> = commands()
.into_iter()
.filter(|(path, classified)| !classified && !deliberate.contains(path.as_str()))
.map(|(path, _)| path)
.collect();
assert!(
accidental.is_empty(),
"unclassified with no entry in UNCLASSIFIED: {accidental:?}"
);
}
#[test]
fn an_output_flag_raises_a_read_command_to_write() {
let spec = spec();
let md = find(&spec, "generate markdown").unwrap();
assert_eq!(md.effect, Some(Read));
let out_file = md.flags.iter().find(|f| f.name == "out-file").unwrap();
assert_eq!(out_file.effect, Some(Write));
let html = md.flags.iter().find(|f| f.name == "html-encode").unwrap();
assert_eq!(html.effect, None);
}
#[test]
fn a_required_output_flag_makes_the_command_write() {
let spec = spec();
let sdk = find(&spec, "generate sdk").unwrap();
assert_eq!(sdk.effect, Some(Write));
assert!(sdk.flags.iter().any(|f| f.name == "output" && f.required));
}
fn find<'a>(spec: &'a usage::Spec, path: &str) -> Option<&'a usage::SpecCommand> {
let mut cmd = &spec.cmd;
for segment in path.split(' ') {
cmd = cmd.subcommands.get(segment)?;
}
Some(cmd)
}
}