use std::io;
use anyhow::{Context, Result};
use clap::CommandFactory;
use clap_complete::{Shell, generate};
use crate::cli::{Cli, CompletionShell};
const BIN_NAME: &str = "decompose";
const SERVICE_CMDS: &[&str] = &[
"start", "stop", "restart", "kill", "logs", "exec", "run", "up",
];
pub fn run_completion(shell: CompletionShell) -> Result<()> {
let clap_shell = to_clap_shell(shell);
let mut cmd = Cli::command();
let mut buf: Vec<u8> = Vec::new();
generate(clap_shell, &mut cmd, BIN_NAME, &mut buf);
let script = String::from_utf8(buf).context("completion script was not valid UTF-8")?;
let final_script = match shell {
CompletionShell::Bash => inject_bash_dynamic(&script),
CompletionShell::Zsh => inject_zsh_dynamic(&script),
_ => script,
};
use io::Write as _;
let stdout = io::stdout();
let mut lock = stdout.lock();
lock.write_all(final_script.as_bytes())
.context("failed to write completion script")?;
Ok(())
}
fn to_clap_shell(shell: CompletionShell) -> Shell {
match shell {
CompletionShell::Bash => Shell::Bash,
CompletionShell::Zsh => Shell::Zsh,
CompletionShell::Fish => Shell::Fish,
CompletionShell::PowerShell => Shell::PowerShell,
CompletionShell::Elvish => Shell::Elvish,
}
}
fn inject_bash_dynamic(script: &str) -> String {
let svc_list = SERVICE_CMDS
.iter()
.map(|s| format!("\"{s}\""))
.collect::<Vec<_>>()
.join(" ");
let snippet = BASH_DYNAMIC_SNIPPET.replace("__SVC_LIST__", &svc_list);
format!("{script}{snippet}")
}
fn inject_zsh_dynamic(script: &str) -> String {
let svc_list = SERVICE_CMDS.join(" ");
let snippet = ZSH_DYNAMIC_SNIPPET.replace("__SVC_LIST__", &svc_list);
format!("{script}{snippet}")
}
const BASH_DYNAMIC_SNIPPET: &str = r#"
# --- decompose dynamic service completion (injected) -------------------------
__decompose_services() {
local svcs
if command -v jq >/dev/null 2>&1; then
svcs=$(decompose config --json 2>/dev/null | jq -r '.processes | keys[]' 2>/dev/null)
else
svcs=$(decompose config --json 2>/dev/null \
| sed -n 's/^ *"\([A-Za-z0-9_][A-Za-z0-9_-]*\)": *{.*/\1/p')
fi
COMPREPLY=( $(compgen -W "${svcs}" -- "${cur}") )
}
__decompose_wrap() {
local cur subcmd i
cur="${COMP_WORDS[COMP_CWORD]}"
subcmd=""
for (( i=1; i<COMP_CWORD; i++ )); do
case "${COMP_WORDS[i]}" in
-*) continue ;;
*) subcmd="${COMP_WORDS[i]}"; break ;;
esac
done
case " __SVC_LIST__ " in
*" \"${subcmd}\" "*)
if [[ "${cur}" != -* ]]; then
__decompose_services
return 0
fi
;;
esac
# Fall back to the clap-generated completion function.
if declare -F _decompose >/dev/null 2>&1; then
_decompose
fi
}
complete -F __decompose_wrap -o bashdefault -o default decompose
# -----------------------------------------------------------------------------
"#;
const ZSH_DYNAMIC_SNIPPET: &str = r#"
# --- decompose dynamic service completion (injected) -------------------------
__decompose_services() {
local -a svcs
local raw
raw=$(decompose config --json 2>/dev/null) || return 0
if (( $+commands[jq] )); then
svcs=(${(f)"$(print -r -- "$raw" | jq -r '.processes | keys[]' 2>/dev/null)"})
else
svcs=(${(f)"$(print -r -- "$raw" | sed -n 's/^ *"\([A-Za-z0-9_][A-Za-z0-9_-]*\)": *{.*/\1/p')"})
fi
if (( ${#svcs} )); then
_values 'service' "${svcs[@]}"
fi
}
__decompose_dyn_wrap() {
local -a service_cmds
service_cmds=(__SVC_LIST__)
local sub
local i
for (( i=2; i<=${#words}; i++ )); do
case "${words[i]}" in
-*) continue ;;
*) sub="${words[i]}"; break ;;
esac
done
if [[ -n "$sub" && "${service_cmds[(r)$sub]}" == "$sub" && "${words[CURRENT]}" != -* ]]; then
__decompose_services
return 0
fi
_decompose "$@"
}
compdef __decompose_dyn_wrap decompose
# -----------------------------------------------------------------------------
"#;