use crate::external::rig;
use clap::builder::PossibleValuesParser;
use clap::{Args, Command, CommandFactory};
use clap_complete::{Shell, generate};
use std::io;
use super::Cli;
#[derive(Args, Debug)]
pub(crate) struct CompletionsArgs {
#[arg(value_enum)]
pub(crate) shell: Shell,
}
impl Cli {
pub fn print_completions(shell: Shell) {
let mut cmd = Cli::command();
if let Some(possible_values) = Self::get_r_version_completions() {
cmd = Self::with_r_version_possible_values(cmd, possible_values);
}
generate(shell, &mut cmd, "arf", &mut io::stdout());
}
fn get_r_version_completions() -> Option<Vec<String>> {
if rig::rig_available().is_err() {
return None;
}
let versions = rig::list_versions().ok()?;
if versions.is_empty() {
return None;
}
let mut values = vec!["default".to_string()];
for v in &versions {
values.push(v.name.clone());
for alias in &v.aliases {
if !values.contains(alias) {
values.push(alias.clone());
}
}
}
Some(values)
}
fn with_r_version_possible_values(mut cmd: Command, possible_values: Vec<String>) -> Command {
let leaked: &'static [String] = Box::leak(possible_values.into_boxed_slice());
let refs: Vec<&'static str> = leaked.iter().map(|s| s.as_str()).collect();
cmd = cmd.mut_arg("r_version", |arg| {
arg.value_parser(PossibleValuesParser::new(refs.iter().copied()))
});
cmd = cmd.mut_subcommand("headless", |subcommand| {
subcommand.mut_arg("r_version", |arg| {
arg.value_parser(PossibleValuesParser::new(refs.iter().copied()))
})
});
cmd = cmd.mut_subcommand("r", |subcommand| {
subcommand.mut_subcommand("resolve", |subcommand| {
subcommand.mut_arg("r_version", |arg| {
arg.value_parser(PossibleValuesParser::new(refs.iter().copied()))
})
})
});
cmd
}
#[cfg(test)]
pub(super) fn generate_completions_string(shell: Shell) -> String {
let mut cmd = Cli::command();
let mut buf = Vec::new();
generate(shell, &mut cmd, "arf", &mut buf);
String::from_utf8(buf).expect("Completions should be valid UTF-8")
}
#[cfg(test)]
pub(super) fn generate_help_string(subcommand_path: &[&str]) -> String {
let mut cmd = Cli::command();
for &name in subcommand_path {
cmd = cmd
.find_subcommand(name)
.expect("Subcommand not found")
.clone();
}
cmd.render_long_help().to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn r_version_possible_values_are_applied_to_all_commands() {
let possible_values = vec!["default".to_string(), "4.5.0".to_string()];
let cmd = Cli::with_r_version_possible_values(Cli::command(), possible_values);
let expected = vec!["default".to_string(), "4.5.0".to_string()];
let r_version_values = |command: &Command| {
command
.get_arguments()
.find(|arg| arg.get_id() == "r_version")
.expect("r_version argument should exist")
.get_possible_values()
.iter()
.map(|value| value.get_name().to_string())
.collect::<Vec<_>>()
};
assert_eq!(r_version_values(&cmd), expected);
let subcommand = cmd
.get_subcommands()
.find(|candidate| candidate.get_name() == "headless")
.expect("subcommand should exist");
assert_eq!(r_version_values(subcommand), expected);
let r_command = cmd
.get_subcommands()
.find(|candidate| candidate.get_name() == "r")
.expect("r subcommand should exist");
let resolve_command = r_command
.get_subcommands()
.find(|candidate| candidate.get_name() == "resolve")
.expect("resolve subcommand should exist");
assert_eq!(r_version_values(resolve_command), expected);
let eval = cmd
.get_arguments()
.find(|arg| arg.get_id() == "eval")
.expect("eval argument should exist");
assert!(
eval.get_possible_values().is_empty(),
"eval should not have possible values"
);
}
}