1use std::process::ExitCode;
11
12use ::usage::Spec;
13use clap::Command;
14
15use crate::formatdoc;
16
17#[must_use]
19pub fn spec(mut command: Command, bin: &str) -> String {
20 command.set_bin_name(bin);
21 let mut spec = Spec::from(&command);
22 spec.name = bin.to_string();
23 spec.bin = bin.to_string();
24 spec.to_string()
25}
26
27#[must_use]
29pub fn mount_line(task: &str) -> String {
30 formatdoc! {r#"#USAGE mount "mise run --quiet {task} -- --usage-spec={task}""#}
31}
32
33#[must_use]
36pub fn take<C: clap::CommandFactory>(default_bin: &str) -> Option<ExitCode> {
37 let bin = spec_bin(std::env::args().skip(1), default_bin)?;
38 print!("{}", spec(C::command(), &bin));
39 Some(ExitCode::SUCCESS)
40}
41
42#[must_use]
45pub fn spec_bin(
46 args: impl IntoIterator<Item = impl AsRef<str>>,
47 default_bin: &str,
48) -> Option<String> {
49 for arg in args {
50 let arg = arg.as_ref();
51 if arg == "--" {
52 break;
53 }
54 if arg == "--usage-spec" {
55 return Some(default_bin.to_owned());
56 }
57 if let Some(bin) = arg.strip_prefix("--usage-spec=") {
58 return Some(if bin.is_empty() {
59 default_bin.to_owned()
60 } else {
61 bin.to_owned()
62 });
63 }
64 }
65 None
66}
67
68#[cfg(test)]
69mod tests {
70 use clap::{CommandFactory, Parser};
71
72 use super::{mount_line, spec, spec_bin};
73
74 #[derive(Parser)]
75 #[command(name = "toy")]
76 struct Toy {
77 #[command(subcommand)]
78 command: ToyCommand,
79 }
80
81 #[derive(clap::Subcommand)]
82 enum ToyCommand {
83 Status,
84 Check,
85 }
86
87 #[test]
88 fn spec_bin_reads_equals_and_bare() {
89 assert_eq!(spec_bin(["--usage-spec"], "qctl").as_deref(), Some("qctl"));
90 assert_eq!(spec_bin(["--usage-spec=q"], "qctl").as_deref(), Some("q"));
91 assert_eq!(spec_bin(["status"], "qctl"), None);
92 assert_eq!(spec_bin(["status", "--", "--usage-spec"], "qctl"), None);
93 assert_eq!(
94 spec_bin(["--usage-spec=q", "--", "status"], "qctl").as_deref(),
95 Some("q")
96 );
97 }
98
99 #[test]
100 fn spec_names_the_mounted_bin() {
101 let text = spec(Toy::command(), "q");
102 assert!(text.contains("name") && text.contains("status"), "{text}");
103 }
104
105 #[test]
106 fn mount_line_is_the_mise_bootstrap() {
107 assert_eq!(
108 mount_line("q"),
109 r#"#USAGE mount "mise run --quiet q -- --usage-spec=q""#
110 );
111 }
112}