1use std::process::ExitCode;
12
13use ::usage::Spec;
14use clap::Command;
15
16use crate::formatdoc;
17
18#[must_use]
20pub fn spec(mut command: Command, bin: &str) -> String {
21 command.set_bin_name(bin);
22 let mut spec = Spec::from(&command);
23 spec.name = bin.to_string();
24 spec.bin = bin.to_string();
25 spec.to_string()
26}
27
28#[must_use]
30pub fn mount_line(task: &str) -> String {
31 formatdoc! {r#"#USAGE mount "mise run --quiet {task} -- --usage-spec={task}""#}
32}
33
34#[must_use]
42pub fn mount_directives(task: &str) -> String {
43 formatdoc! {"
44 #MISE raw=true
45 #MISE raw_args=true
46 {mount}",
47 mount = mount_line(task),
48 }
49}
50
51#[must_use]
54pub fn take<C: clap::CommandFactory>(default_bin: &str) -> Option<ExitCode> {
55 let bin = spec_bin(std::env::args().skip(1), default_bin)?;
56 print!("{}", spec(C::command(), &bin));
57 Some(ExitCode::SUCCESS)
58}
59
60#[must_use]
63pub fn spec_bin(
64 args: impl IntoIterator<Item = impl AsRef<str>>,
65 default_bin: &str,
66) -> Option<String> {
67 for arg in args {
68 let arg = arg.as_ref();
69 if arg == "--" {
70 break;
71 }
72 if arg == "--usage-spec" {
73 return Some(default_bin.to_owned());
74 }
75 if let Some(bin) = arg.strip_prefix("--usage-spec=") {
76 return Some(if bin.is_empty() {
77 default_bin.to_owned()
78 } else {
79 bin.to_owned()
80 });
81 }
82 }
83 None
84}
85
86#[cfg(test)]
87mod tests {
88 use clap::{CommandFactory, Parser};
89
90 use super::{mount_directives, mount_line, spec, spec_bin};
91
92 #[derive(Parser)]
93 #[command(name = "toy")]
94 struct Toy {
95 #[command(subcommand)]
96 command: ToyCommand,
97 }
98
99 #[derive(clap::Subcommand)]
100 enum ToyCommand {
101 Status,
102 Check,
103 }
104
105 #[test]
106 fn spec_bin_reads_equals_and_bare() {
107 assert_eq!(spec_bin(["--usage-spec"], "qctl").as_deref(), Some("qctl"));
108 assert_eq!(spec_bin(["--usage-spec=q"], "qctl").as_deref(), Some("q"));
109 assert_eq!(spec_bin(["status"], "qctl"), None);
110 assert_eq!(spec_bin(["status", "--", "--usage-spec"], "qctl"), None);
111 assert_eq!(
112 spec_bin(["--usage-spec=q", "--", "status"], "qctl").as_deref(),
113 Some("q")
114 );
115 }
116
117 #[test]
118 fn spec_names_the_mounted_bin() {
119 let text = spec(Toy::command(), "q");
120 assert!(text.contains("name") && text.contains("status"), "{text}");
121 }
122
123 #[test]
124 fn mount_line_is_the_mise_bootstrap() {
125 assert_eq!(
126 mount_line("q"),
127 r#"#USAGE mount "mise run --quiet q -- --usage-spec=q""#
128 );
129 }
130
131 #[test]
132 fn mount_directives_end_with_the_mount_line() {
133 let directives = mount_directives("q");
134 assert_eq!(
135 directives.lines().collect::<Vec<_>>(),
136 [
137 "#MISE raw=true",
138 "#MISE raw_args=true",
139 mount_line("q").as_str()
140 ]
141 );
142 }
143}