use crate::cli::{Args, CompletionCommand};
use clap::CommandFactory;
use miette::IntoDiagnostic;
use regex::Regex;
use std::borrow::Cow;
use std::io::Write;
pub(crate) fn execute(args: CompletionCommand) -> miette::Result<()> {
let clap_shell = args
.shell
.or(clap_complete::Shell::from_env())
.unwrap_or(clap_complete::Shell::Bash);
let script = get_completion_script(clap_shell);
let script = match clap_shell {
clap_complete::Shell::Bash => replace_bash_completion(&script),
clap_complete::Shell::Zsh => replace_zsh_completion(&script),
_ => Cow::Owned(script),
};
std::io::stdout()
.write_all(script.as_bytes())
.into_diagnostic()?;
Ok(())
}
fn get_completion_script(shell: clap_complete::Shell) -> String {
let mut buf = vec![];
clap_complete::generate(shell, &mut Args::command(), "pixi", &mut buf);
String::from_utf8(buf).expect("clap_complete did not generate a valid UTF8 script")
}
fn replace_bash_completion(script: &str) -> Cow<str> {
let pattern = r#"(?s)pixi__run\).*?opts="(.*?)".*?(if.*?fi)"#;
let replacement = r#"pixi__run)
opts="$1"
if [[ $${cur} == -* ]] ; then
COMPREPLY=( $$(compgen -W "$${opts}" -- "$${cur}") )
return 0
elif [[ $${COMP_CWORD} -eq 2 ]]; then
local tasks=$$(pixi task list --summary 2> /dev/null)
if [[ $$? -eq 0 ]]; then
COMPREPLY=( $$(compgen -W "$${tasks}" -- "$${cur}") )
return 0
fi
fi"#;
let re = Regex::new(pattern).unwrap();
re.replace(script, replacement)
}
fn replace_zsh_completion(script: &str) -> Cow<str> {
let pattern = r"(?ms)(\(run\))(?:.*?)(_arguments.*?)(\*::task)";
let zsh_replacement = r#"$1
local tasks
tasks=("$${(@s/ /)$$(pixi task list --summary 2> /dev/null)}")
if [[ -n "$$tasks" ]]; then
_values 'task' "$${tasks[@]}"
else
return 1
fi
$2::task"#;
let re = Regex::new(pattern).unwrap();
re.replace(script, zsh_replacement)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_zsh_completion() {
let script = r#"
(add)
_arguments "${_arguments_options[@]}" \
'--manifest-path=[The path to '\''pixi.toml'\'']:MANIFEST_PATH:_files' \
'*::specs -- Specify the dependencies you wish to add to the project:' \
&& ret=0
;;
(run)
_arguments "${_arguments_options[@]}" \
'--manifest-path=[The path to '\''pixi.toml'\'']:MANIFEST_PATH:_files' \
'--color=[Whether the log needs to be colored]:COLOR:(always never auto)' \
'(--frozen)--locked[Require pixi.lock is up-to-date]' \
'(--locked)--frozen[Don'\''t check if pixi.lock is up-to-date, install as lockfile states]' \
'*-v[More output per occurrence]' \
'*--verbose[More output per occurrence]' \
'(-v --verbose)*-q[Less output per occurrence]' \
'(-v --verbose)*--quiet[Less output per occurrence]' \
'-h[Print help]' \
'--help[Print help]' \
'*::task -- The task you want to run in the projects environment:' \
&& ret=0
;;
(add)
_arguments "${_arguments_options[@]}" \
&& ret=0
;;
(run)
_arguments "${_arguments_options[@]}" \
&& ret=0
;;
(shell)
_arguments "${_arguments_options[@]}" \
&& ret=0
;;
"#;
let result = replace_zsh_completion(script);
insta::assert_snapshot!(result);
}
#[test]
pub fn test_bash_completion() {
let script = r#"
pixi__project__help__help)
opts=""
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
pixi__run)
opts="-v -q -h --manifest-path --locked --frozen --verbose --quiet --color --help [TASK]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
--manifest-path)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--color)
COMPREPLY=($(compgen -W "always never auto" -- "${cur}"))
return 0
;;
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
pixi__search)
opts="-c -l -v -q -h --channel --color --help <PACKAGE>"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
fi
case "${prev}" in
--channel)
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
;;
"#;
let result = replace_bash_completion(script);
insta::assert_snapshot!(result);
}
#[test]
pub fn test_bash_completion_working_regex() {
let script = get_completion_script(clap_complete::Shell::Bash);
assert_ne!(replace_bash_completion(&script), script);
}
#[test]
pub fn test_zsh_completion_working_regex() {
let script = get_completion_script(clap_complete::Shell::Zsh);
assert_ne!(replace_zsh_completion(&script), script);
}
}