Skip to main content

usage/complete/
mod.rs

1use crate::error::UsageErr;
2use crate::Spec;
3
4mod bash;
5mod fish;
6mod nu;
7mod powershell;
8mod zsh;
9
10/// Options for generating shell completion scripts.
11pub struct CompleteOptions {
12    /// Path to the `usage` binary (e.g., "usage" or "/usr/local/bin/usage").
13    pub usage_bin: String,
14    /// Target shell: "bash", "fish", "zsh", or "powershell".
15    pub shell: String,
16    /// Name of the CLI binary to generate completions for.
17    pub bin: String,
18    /// Optional cache key (e.g., version) to avoid regenerating the spec file.
19    pub cache_key: Option<String>,
20    /// The usage spec to embed directly in the completion script.
21    pub spec: Option<Spec>,
22    /// Command to run to generate the usage spec dynamically.
23    pub usage_cmd: Option<String>,
24    /// Whether to include the bash-completion library sourcing (bash only).
25    pub include_bash_completion_lib: bool,
26    /// Source file path for the `@generated` comment.
27    pub source_file: Option<String>,
28}
29
30/// Generates a shell completion script for the specified shell.
31///
32/// # Arguments
33/// * `options` - Configuration options including target shell and spec source
34///
35/// # Returns
36/// The generated completion script as a string, or an error if the shell is unsupported.
37///
38/// # Supported Shells
39/// - `bash` - Bash completion using `complete` builtin
40/// - `fish` - Fish shell completions
41/// - `zsh` - Zsh completion using `compdef`
42/// - `powershell` - PowerShell completion using `Register-ArgumentCompleter`
43pub fn complete(options: &CompleteOptions) -> Result<String, UsageErr> {
44    match options.shell.as_str() {
45        "bash" => Ok(bash::complete_bash(options)),
46        "fish" => Ok(fish::complete_fish(options)),
47        "nu" => Ok(nu::complete_nu(options)),
48        "powershell" => Ok(powershell::complete_powershell(options)),
49        "zsh" => Ok(zsh::complete_zsh(options)),
50        _ => Err(UsageErr::UnsupportedShell(options.shell.clone())),
51    }
52}
53
54/// Generates a shell-specific "init" script that enables tab-completion for any
55/// command on `$PATH` whose first line is a `usage` shebang, without requiring
56/// per-script `usage g completion` generation. The user sources this once from
57/// their shell rc.
58///
59/// # Supported Shells
60/// - `bash` - registers a `complete -D` default handler
61/// - `zsh` - registers a `compdef -default-` fallback handler
62/// - `fish` - scans `$PATH` once at startup and registers per-command completers
63pub fn complete_init(shell: &str, usage_bin: &str) -> Result<String, UsageErr> {
64    match shell {
65        "bash" => Ok(bash::complete_bash_init(usage_bin)),
66        "fish" => Ok(fish::complete_fish_init(usage_bin)),
67        "zsh" => Ok(zsh::complete_zsh_init(usage_bin)),
68        _ => Err(UsageErr::UnsupportedShell(shell.to_string())),
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn complete_init_supported_shells() {
78        for shell in ["bash", "zsh", "fish"] {
79            let out = complete_init(shell, "usage").expect("supported shell");
80            assert!(!out.is_empty(), "{shell} init should not be empty");
81        }
82    }
83
84    #[test]
85    fn complete_init_rejects_unsupported_shell() {
86        let err = complete_init("nu", "usage").expect_err("nu has no init script");
87        assert!(matches!(err, UsageErr::UnsupportedShell(ref s) if s == "nu"));
88    }
89}