use crate::cli::Cli;
use eyre::Result;
#[derive(Debug, usage_rs::Args)]
#[usage(verbatim_doc_comment, hide = true)]
pub(crate) struct Usage {}
fn prepare_task_runner(command: &mut usage::SpecCommand) {
command.args.clear();
for flag in &mut command.flags {
flag.conflicts.retain(|selector| selector != "TASK");
flag.requires.retain(|selector| selector != "TASK");
flag.required_if.retain(|selector| selector != "TASK");
flag.required_unless.retain(|selector| selector != "TASK");
}
let mut mount = usage::SpecMount::new("mise tasks --usage".to_string());
mount.synopsis = Some("[TASK] [ARGS]…".to_string());
command.mounts.push(mount);
command.restart_token = Some(":::".to_string());
}
pub(super) fn spec() -> usage::Spec {
{
let mut spec: usage::Spec = Cli::to_kdl().parse().expect("generated mise usage spec");
spec.default_subcommand = Some("run".to_string());
if let Some(run) = spec.cmd.subcommands.get_mut("run") {
prepare_task_runner(run);
}
if let Some(tasks_run) = spec
.cmd
.subcommands
.get_mut("tasks")
.and_then(|tasks| tasks.subcommands.get_mut("run"))
{
prepare_task_runner(tasks_run);
}
crate::cli::command_effects::apply(&mut spec);
spec.restamp();
spec
}
}
pub(super) fn completion_spec() -> usage::Spec {
let mut spec = spec();
let extra: usage::Spec = include_str!("../assets/mise-extra.usage.kdl")
.parse()
.expect("mise completion metadata should parse");
spec.merge(extra);
spec
}
impl Usage {
pub(crate) fn run(self) -> Result<()> {
let min_version = r#"min_usage_version "6.8""#;
println!("{min_version}\n{}", completion_spec().to_string().trim());
Ok(())
}
}
#[cfg(test)]
mod tests {
#[test]
fn example_descriptions_are_not_shell_input() {
let spec = super::spec();
for name in ["prune", "set", "watch", "en", "registry", "reshim"] {
for example in &spec.cmd.subcommands[name].examples {
for line in example.code.lines() {
assert!(
!line.starts_with("rm -rf ")
&& !line.starts_with("Runs the ")
&& !line.starts_with("Skip loading ")
&& !line.starts_with("Enter value for ")
&& line != "v20.0.0"
&& line != "core:node"
&& !line.ends_with("Encryption:"),
"{name}: {line}"
);
}
}
}
}
#[test]
fn task_mounts_describe_arguments_without_discovery() {
let spec = super::spec();
for cmd in [
&spec.cmd.subcommands["run"],
&spec.cmd.subcommands["tasks"].subcommands["run"],
] {
assert_eq!(cmd.mounts[0].synopsis.as_deref(), Some("[TASK] [ARGS]…"));
assert!(cmd.usage.ends_with("[TASK] [ARGS]…"), "{}", cmd.usage);
let page = usage::docs::markdown::MarkdownRenderer::new(spec.clone())
.with_link_extension(".html")
.render_cmd(cmd)
.unwrap();
assert!(page.contains("[TASK] [ARGS]…"), "{page}");
}
}
#[test]
fn command_examples_reach_cli_help_and_markdown() {
let spec = super::spec();
let markdown = usage::docs::markdown::MarkdownRenderer::new(spec.clone());
for name in ["activate", "run", "install", "env", "use"] {
let cmd = &spec.cmd.subcommands[name];
assert!(
!cmd.examples.is_empty(),
"{name} has no structured examples"
);
let help = usage::docs::cli::render_help(&spec, cmd, true);
assert_eq!(help.matches("Examples:").count(), 1, "{help}");
let page = markdown.render_cmd(cmd).unwrap();
assert_eq!(page.matches("## Examples").count(), 1, "{page}");
for example in &cmd.examples {
assert!(!example.code.contains("<bold>"));
assert!(!example.code.contains('\u{1b}'));
for line in example.code.lines() {
assert!(help.contains(line), "{name}: {line}");
}
assert!(page.contains(&example.code), "{name}: {}", example.code);
}
}
}
#[test]
fn command_examples_survive_spec_roundtrip() {
let spec = super::spec();
let reparsed: usage::Spec = spec.to_string().parse().unwrap();
for name in ["activate", "run", "install", "env", "use"] {
let expected = &spec.cmd.subcommands[name].examples;
let actual = &reparsed.cmd.subcommands[name].examples;
assert_eq!(actual.len(), expected.len(), "{name}");
for (actual, expected) in actual.iter().zip(expected) {
assert_eq!(actual.code, expected.code, "{name}");
assert_eq!(actual.header, expected.header, "{name}");
assert_eq!(actual.help, expected.help, "{name}");
assert_eq!(actual.lang, expected.lang, "{name}");
}
}
}
#[test]
fn command_examples_reach_manpage() {
let spec = super::spec();
let manpage = usage::docs::manpage::ManpageRenderer::new(spec.clone())
.render()
.unwrap();
for name in ["activate", "run", "install", "env", "use"] {
for example in &spec.cmd.subcommands[name].examples {
let escaped = example.code.replace('\\', "\\\\").replace('-', "\\-");
assert!(manpage.contains(&escaped), "{name}: {}", example.code);
}
}
}
}