use std::io::Write;
use clap_complete::engine::CompletionCandidate;
use cerulion_cli_engine::completions;
fn map(candidates: Vec<completions::Candidate>) -> Vec<CompletionCandidate> {
candidates
.into_iter()
.map(|c| {
let candidate = CompletionCandidate::new(c.value);
match c.help {
Some(help) => candidate.help(Some(help.into())),
None => candidate,
}
})
.collect()
}
pub fn topics() -> Vec<CompletionCandidate> {
map(completions::complete_topic_names())
}
pub fn node_types() -> Vec<CompletionCandidate> {
map(completions::complete_node_types())
}
pub fn resim_selections() -> Vec<CompletionCandidate> {
vec![CompletionCandidate::new("all")
.help(Some("re-execute every node in the bag's graph".into()))]
}
pub fn graph_names() -> Vec<CompletionCandidate> {
map(completions::complete_graph_names())
}
pub fn schema_names() -> Vec<CompletionCandidate> {
map(completions::complete_schema_names())
}
pub fn robot_names() -> Vec<CompletionCandidate> {
map(completions::complete_robot_names())
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, clap::ValueEnum)]
pub enum CompletionShell {
Bash,
Elvish,
Fish,
Powershell,
Zsh,
}
impl CompletionShell {
fn as_str(self) -> &'static str {
match self {
Self::Bash => "bash",
Self::Elvish => "elvish",
Self::Fish => "fish",
Self::Powershell => "powershell",
Self::Zsh => "zsh",
}
}
pub fn install_hint(self) -> String {
match self {
Self::Bash => "echo 'source <(COMPLETE=bash cerulion)' >> ~/.bashrc".to_string(),
Self::Elvish => {
"echo 'eval (E:COMPLETE=elvish cerulion | slurp)' >> ~/.elvish/rc.elv".to_string()
}
Self::Fish => concat!(
"echo 'COMPLETE=fish cerulion | source' > ",
"~/.config/fish/completions/cerulion.fish"
)
.to_string(),
Self::Powershell => concat!(
r#"echo '$env:COMPLETE = "powershell"; cerulion | Out-String | "#,
r#"Invoke-Expression; Remove-Item Env:\COMPLETE' >> $PROFILE"#
)
.to_string(),
Self::Zsh => concat!(
"echo 'autoload -Uz compinit && (( $+functions[compdef] )) || compinit' ",
">> ~/.zshrc\n",
"echo 'source <(COMPLETE=zsh cerulion)' >> ~/.zshrc"
)
.to_string(),
}
}
}
pub fn write_registration_script(
shell: CompletionShell,
out: &mut dyn Write,
) -> std::io::Result<()> {
let name = shell.as_str();
let shells = clap_complete::env::Shells::builtins();
let completer = shells
.completer(name)
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::Unsupported,
format!("no completion generator is registered for shell '{name}'"),
)
})?;
let self_path = std::env::current_exe()
.ok()
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_else(|| "cerulion".to_string());
completer.write_registration("COMPLETE", "cerulion", "cerulion", &self_path, out)
}
#[cfg(test)]
mod tests {
use super::*;
use clap::ValueEnum;
#[test]
fn every_declared_shell_has_a_generator_and_emits_a_registration_script() {
let all = CompletionShell::value_variants();
assert_eq!(all.len(), 5, "expected 5 shells, got {all:?}");
for shell in all {
let mut buf = Vec::new();
write_registration_script(*shell, &mut buf)
.unwrap_or_else(|e| panic!("{shell:?} must have a generator: {e}"));
let script = String::from_utf8(buf).expect("script must be UTF-8");
assert!(
!script.is_empty(),
"{shell:?} emitted an empty registration script"
);
assert!(
script.contains("COMPLETE"),
"{shell:?} script must set the COMPLETE variable:\n{script}"
);
assert!(
script.contains(shell.as_str()),
"{shell:?} script must carry its own shell token:\n{script}"
);
assert!(
script.contains("cerulion"),
"{shell:?} script must name the `cerulion` command:\n{script}"
);
}
}
#[test]
fn each_install_hint_names_its_own_shell_token_and_the_binary() {
for shell in CompletionShell::value_variants() {
let hint = shell.install_hint();
assert!(
hint.contains(shell.as_str()),
"{shell:?} hint must set COMPLETE to its own token: {hint}"
);
assert!(
hint.contains("cerulion"),
"{shell:?} hint must invoke the binary: {hint}"
);
}
}
#[test]
fn the_zsh_hint_guards_compinit_because_compdef_is_not_a_builtin() {
let hint = CompletionShell::Zsh.install_hint();
let lines: Vec<&str> = hint.lines().collect();
assert_eq!(
lines,
vec![
"echo 'autoload -Uz compinit && (( $+functions[compdef] )) || compinit' >> ~/.zshrc",
"echo 'source <(COMPLETE=zsh cerulion)' >> ~/.zshrc",
],
"the zsh hint changed — re-verify it on a compinit-less zsh before \
re-blessing this oracle"
);
}
#[test]
fn the_fish_hint_is_the_single_canonical_form() {
assert_eq!(
CompletionShell::Fish.install_hint(),
"echo 'COMPLETE=fish cerulion | source' > ~/.config/fish/completions/cerulion.fish"
);
}
}