diff --git a/Cargo.lock b/Cargo.lock index 367b1d00..e2432d37 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -577,6 +577,15 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_complete" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" +dependencies = [ + "clap", +] + [[package]] name = "clap_derive" version = "4.5.0" @@ -2023,6 +2032,7 @@ dependencies = [ "built", "chrono", "clap", + "clap_complete", "dialoguer", "dirs", "dropshot", diff --git a/Cargo.toml b/Cargo.toml index dd633fa1..f7eaff59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ base64 = "0.21.7" built = { version = "0.7.1", features = ["git2"] } chrono = { version = "0.4.34", features = ["serde"] } clap = { version = "4.5.1", features = ["derive", "string", "env"] } +clap_complete = "4.5.1" dialoguer = "0.10.4" dirs = "4.0.0" dropshot = { git = "https://github.com/oxidecomputer/dropshot" } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 2a3047a6..b656df8d 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -23,6 +23,7 @@ async-trait = { workspace = true } base64 = { workspace = true } chrono = { workspace = true } clap = { workspace = true } +clap_complete = { workspace = true } dialoguer = { workspace = true } dirs = { workspace = true } env_logger = { workspace = true } diff --git a/cli/docs/cli.json b/cli/docs/cli.json index 2eceb36d..ad6fcf70 100644 --- a/cli/docs/cli.json +++ b/cli/docs/cli.json @@ -222,6 +222,25 @@ } ] }, + { + "name": "completion", + "about": "Generate shell completion scripts for Oxide CLI commands.", + "long_about": "Generate shell completion scripts for Oxide CLI commands.\n\nThis command generates scripts for various shells that can be used to\nenable completion.\n\n## Installation\n\n### Bash\n\nAdd this to your `~/.bash_profile`:\n\n```sh\neval \"$(oxide completion -s bash)\"\n```\n\n### Zsh\n\nGenerate an `_oxide` completion script and put it somewhere in your\n`$fpath`, for example:\n\n```sh\noxide completion -s zsh > ~/.zfunc/_oxide\n```\n\nand check that you have the following lines in your `~/.zshrc`:\n\n```sh\nautoload -U compinit\ncompinit -i\n```\n\n### Fish\n\nGenerate an `oxide.fish` completion script:\n\n```sh\noxide completion -s fish > ~/.config/fish/completions/oxide.fish\n```\n\n### PowerShell\n\nOpen your profile script with:\n\n```sh\nmkdir -Path (Split-Path -Parent $profile)\nnotepad $profile\n```\n\nAdd the following line and save the file:\n\n```powershell\nInvoke-Expression -Command $(oxide completion -s powershell | Out-String)\n```\n\n### Elvish\n\nGenerate an `oxide.elv` completion script and put it in a module search\ndirectory, for example:\n\n```sh\noxide completion -s elvish > ~/.local/share/elvish/lib/oxide.elv\n```\n\nand import this by adding the following to `~/.config/elvish/rc.elv`\n\n```\nuse oxide\n```", + "args": [ + { + "long": "shell", + "short": "s", + "values": [ + "bash", + "elvish", + "fish", + "powershell", + "zsh" + ], + "help": "Shell type" + } + ] + }, { "name": "current-user", "subcommands": [ diff --git a/cli/src/cmd_completion.rs b/cli/src/cmd_completion.rs new file mode 100644 index 00000000..acebc877 --- /dev/null +++ b/cli/src/cmd_completion.rs @@ -0,0 +1,101 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Copyright 2024 Oxide Computer Company + +use crate::RunnableCmd; +use anyhow::Result; +use async_trait::async_trait; +use clap::Parser; +use clap_complete::{generate, Shell}; +use std::io; + +/// Generate shell completion scripts for Oxide CLI commands. +/// +/// This command generates scripts for various shells that can be used to +/// enable completion. +/// +/// ## Installation +/// +/// ### Bash +/// +/// Add this to your `~/.bash_profile`: +/// +/// ```sh +/// eval "$(oxide completion -s bash)" +/// ``` +/// +/// ### Zsh +/// +/// Generate an `_oxide` completion script and put it somewhere in your +/// `$fpath`, for example: +/// +/// ```sh +/// oxide completion -s zsh > ~/.zfunc/_oxide +/// ``` +/// +/// and check that you have the following lines in your `~/.zshrc`: +/// +/// ```sh +/// autoload -U compinit +/// compinit -i +/// ``` +/// +/// ### Fish +/// +/// Generate an `oxide.fish` completion script: +/// +/// ```sh +/// oxide completion -s fish > ~/.config/fish/completions/oxide.fish +/// ``` +/// +/// ### PowerShell +/// +/// Open your profile script with: +/// +/// ```sh +/// mkdir -Path (Split-Path -Parent $profile) +/// notepad $profile +/// ``` +/// +/// Add the following line and save the file: +/// +/// ```powershell +/// Invoke-Expression -Command $(oxide completion -s powershell | Out-String) +/// ``` +/// +/// ### Elvish +/// +/// Generate an `oxide.elv` completion script and put it in a module search +/// directory, for example: +/// +/// ```sh +/// oxide completion -s elvish > ~/.local/share/elvish/lib/oxide.elv +/// ``` +/// +/// and import this by adding the following to `~/.config/elvish/rc.elv` +/// +/// ``` +/// use oxide +/// ``` +#[derive(Parser, Debug, Clone)] +#[command(verbatim_doc_comment)] +#[command(name = "generate-completions")] +pub struct CmdCompletion { + /// Shell type + #[clap(short, long)] + shell: Shell, +} + +#[async_trait] +impl RunnableCmd for CmdCompletion { + async fn run(&self, _ctx: &oxide::context::Context) -> Result<()> { + let cli = crate::make_cli(); + let mut cmd = cli.command_take(); + let name = cmd.get_name().to_string(); + generate(self.shell, &mut cmd, name, &mut io::stdout()); + + Ok(()) + } +} diff --git a/cli/src/main.rs b/cli/src/main.rs index 9cef30be..dc750a41 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -19,6 +19,7 @@ use oxide::types::{IdpMetadataSource, IpRange, Ipv4Range, Ipv6Range}; mod cli_builder; mod cmd_api; mod cmd_auth; +mod cmd_completion; mod cmd_disk; mod cmd_docs; mod cmd_instance; @@ -47,6 +48,7 @@ pub fn make_cli() -> NewCli<'static> { .add_custom::("disk import") .add_custom::("instance serial") .add_custom::("instance from-image") + .add_custom::("completion") } #[tokio::main]