Repository: FuelLabs/fuelup
Pull request #293 (2022-11-28 14:29:02)

feat: fuelup completions
Closes https://github.com/FuelLabs/fuelup/issues/291.

0xYYY: hi @bingcicle, thanks for your suggestions, i've updated accordingly

(book content for `fuelup completions` is adapted from https://fuellabs.github.io/sway/master/forc/commands/forc_completions.html)

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 59ab49fd..58e96899 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -99,6 +99,15 @@ dependencies = [
  "textwrap",
 ]
 
+[[package]]
+name = "clap_complete"
+version = "3.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f7a2e0a962c45ce25afce14220bc24f9dade0a1787f185cecf96bfba7847cd8"
+dependencies = [
+ "clap",
+]
+
 [[package]]
 name = "clap_derive"
 version = "3.2.15"
@@ -253,6 +262,7 @@ version = "0.12.2"
 dependencies = [
  "anyhow",
  "clap",
+ "clap_complete",
  "component",
  "dirs",
  "flate2",
diff --git a/Cargo.toml b/Cargo.toml
index 716f73b0..505a94e8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,6 +20,7 @@ path = "src/main.rs"
 [dependencies]
 anyhow = "1"
 clap = { version = "3.2", features = ["cargo", "derive", "env"] }
+clap_complete = "3.2"
 component = { path = "component" }
 dirs = "4"
 flate2 = "1"
diff --git a/docs/src/configuration.md b/docs/src/configuration.md
index 1007d996..2ac0c53c 100644
--- a/docs/src/configuration.md
+++ b/docs/src/configuration.md
@@ -3,3 +3,111 @@
 _fuelup_ has a [TOML](https://github.com/toml-lang/toml) settings file at
 `.fuelup/settings.toml`. The schema for this file is not part of the public
 interface for _fuelup_ - the fuelup CLI should be used to query and set settings.
+
+## Generate Shell Completions
+
+Enable tab completion for Bash, Fish, Zsh, or PowerShell. The script prints output on `stdout`,
+allowing one to re-direct the output to the file of their choosing. Where you place the file will
+depend on which shell, and which operating system you are using. Your particular configuration may
+also determine where these scripts need to be placed.
+
+Here are some common set ups for the supported shells under Unix and similar operating systems
+(such as GNU/Linux). For these settings to take effect, you may have to log out and log back in to
+your shell session.
+
+### BASH
+
+Completion files are commonly stored in `/etc/bash_completion.d/` for system-wide commands, but can
+be stored in `~/.local/share/bash-completion/completions` for user-specific commands.
+
+```sh
+mkdir -p ~/.local/share/bash-completion/completions
+fuelup completions --shell=bash >> ~/.local/share/bash-completion/completions/fuelup
+```
+
+## BASH (macOS/Homebrew)
+
+Homebrew stores bash completion files within the Homebrew directory. With the `bash-completion` brew
+formula installed.
+
+```sh
+mkdir -p $(brew --prefix)/etc/bash_completion.d
+fuelup completions --shell=bash > $(brew --prefix)/etc/bash_completion.d/fuelup.bash-completion
+```
+
+### FISH
+
+Fish completion files are commonly stored in `$HOME/.config/fish/completions`.
+
+```sh
+mkdir -p ~/.config/fish/completions
+fuelup completions --shell=fish > ~/.config/fish/completions/fuelup.fish
+```
+
+### ZSH
+
+ZSH completions are commonly stored in any directory listed in your `$fpath` variable. To use these
+completions, you must either add the generated script to one of those directories, or add your own
+to this list.
+
+Adding a custom directory is often the safest bet if you are unsure of which directory to use. First
+create the directory; for this example we'll create a hidden directory inside our `$HOME` directory:
+
+```sh
+mkdir ~/.zfunc
+```
+
+Then add the following lines to your `.zshrc` just before `compinit`:
+
+```sh
+fpath+=~/.zfunc
+```
+
+Now you can install the completions script using the following command:
+
+```sh
+fuelup completions --shell=zsh > ~/.zfunc/_fuelup
+```
+
+### POWERSHELL
+
+The powershell completion scripts require PowerShell v5.0+ (which comes with Windows 10, but can be
+downloaded separately for windows 7 or 8.1).
+
+First, check if a profile has already been set
+
+```sh
+Test-Path $profile
+```
+
+If the above command returns `False` run the following
+
+```sh
+New-Item -path $profile -type file -force
+```
+
+Now open the file provided by `$profile` (if you used the `New-Item` command it will be
+`${env:USERPROFILE}\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1`).
+
+Next, we either save the completions file into our profile, or into a separate file and source it
+inside our profile. To save the completions into our profile simply use
+
+```sh
+fuelup completions --shell=powershell >> ${env:USERPROFILE}\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
+```
+
+### ELVISH
+
+Elvish completions are commonly stored in [`epm`](https://elv.sh/ref/epm.html#the-epm-managed-directory)-managed
+directories.
+
+```sh
+fuelup completions --shell=elvish > ~/.local/share/elvish/lib/fuelup.elv
+```
+
+Then in [`rc.elv`](https://elv.sh/ref/command.html#rc-file), add the following line to activate the
+generated completions.
+
+```sh
+use fuelup
+```
diff --git a/docs/src/examples.md b/docs/src/examples.md
index 9fca544d..47392dd6 100644
--- a/docs/src/examples.md
+++ b/docs/src/examples.md
@@ -13,6 +13,7 @@
 | `fuelup check`                            | Checks for updates to distributable toolchains                                           |
 | `fuelup show`                             | Shows the active toolchain and installed toolchains, as well as the host and fuelup home |
 | `fuelup toolchain help`                   | Shows the `help` page for a subcommand (like `toolchain`)                                |
+| `fuelup completions --shell=zsh`          | Generate shell completions for ZSH                                                       |
 
 [forc]: https://github.com/FuelLabs/sway/tree/master/forc
 [fuel-core]: https://github.com/FuelLabs/fuel-core
diff --git a/fuelup-init.sh b/fuelup-init.sh
index 8c717a18..e043b086 100755
--- a/fuelup-init.sh
+++ b/fuelup-init.sh
@@ -137,7 +137,7 @@ main() {
     ignore rmdir "$_dir"
 
     printf '\n'
-    printf '%s\n' "fuelup ${_fuelup_version} has been installed in $FUELUP_DIR/bin. To fetch the latest toolchain containing the forc and fuel-core binaries, run 'fuelup toolchain install latest'." 1>&2
+    printf '%s\n' "fuelup ${_fuelup_version} has been installed in $FUELUP_DIR/bin. To fetch the latest toolchain containing the forc and fuel-core binaries, run 'fuelup toolchain install latest'. To generate completions for your shell, run 'fuelup completions --shell=SHELL'." 1>&2
 
     if [ "$allow_modify" = "yes" ]; then
         if echo "$PATH" | grep -q "$FUELUP_DIR/bin"; then
diff --git a/src/commands/completions.rs b/src/commands/completions.rs
new file mode 100644
index 00000000..c42109ad
--- /dev/null
+++ b/src/commands/completions.rs
@@ -0,0 +1,21 @@
+use anyhow::Result;
+use clap::Parser;
+use clap_complete::Shell;
+
+use crate::ops::fuelup_completions;
+
+/// Generate tab-completion scripts for your shell
+#[derive(Debug, Parser)]
+pub struct CompletionsCommand {
+    /// Specify shell to enable tab-completion for
+    ///
+    /// [possible values: zsh, bash, fish, powershell, elvish]
+    #[clap(short = 'S', long)]
+    pub shell: Shell,
+}
+
+pub fn exec(command: CompletionsCommand) -> Result<()> {
+    fuelup_completions::completions(command)?;
+
+    Ok(())
+}
diff --git a/src/commands/mod.rs b/src/commands/mod.rs
index 483446fb..643631f3 100644
--- a/src/commands/mod.rs
+++ b/src/commands/mod.rs
@@ -1,4 +1,5 @@
 pub mod check;
+pub mod completions;
 pub mod component;
 pub mod default;
 pub mod fuelup;
diff --git a/src/fuelup_cli.rs b/src/fuelup_cli.rs
index a2f26d59..efa51269 100644
--- a/src/fuelup_cli.rs
+++ b/src/fuelup_cli.rs
@@ -2,9 +2,10 @@ use anyhow::Result;
 use clap::Parser;
 
 use crate::commands::show::ShowCommand;
-use crate::commands::{check, component, default, fuelup, show, toolchain, update};
+use crate::commands::{check, completions, component, default, fuelup, show, toolchain, update};
 
 use crate::commands::check::CheckCommand;
+use crate::commands::completions::CompletionsCommand;
 use crate::commands::component::ComponentCommand;
 use crate::commands::default::DefaultCommand;
 use crate::commands::fuelup::FuelupCommand;
@@ -13,7 +14,7 @@ use crate::commands::update::UpdateCommand;
 
 #[derive(Debug, Parser)]
 #[clap(name = "fuelup", about = "Fuel Toolchain Manager", version)]
-struct Cli {
+pub struct Cli {
     #[clap(subcommand)]
     command: Commands,
 }
@@ -22,6 +23,8 @@ struct Cli {
 enum Commands {
     /// Check for updates to Fuel toolchains and fuelup
     Check(CheckCommand),
+    /// Generate shell completions
+    Completions(CompletionsCommand),
     /// Add or remove components from the currently active toolchain
     #[clap(subcommand)]
     Component(ComponentCommand),
@@ -44,6 +47,7 @@ pub fn fuelup_cli() -> Result<()> {
 
     match cli.command {
         Commands::Check(command) => check::exec(command),
+        Commands::Completions(command) => completions::exec(command),
         Commands::Component(command) => component::exec(command),
         Commands::Default_(command) => default::exec(command),
         Commands::Fuelup(command) => match command {
diff --git a/src/ops/fuelup_completions.rs b/src/ops/fuelup_completions.rs
new file mode 100644
index 00000000..48ca24bf
--- /dev/null
+++ b/src/ops/fuelup_completions.rs
@@ -0,0 +1,11 @@
+use crate::commands::completions::CompletionsCommand;
+use anyhow::Result;
+use clap::CommandFactory;
+use clap_complete::generate;
+
+pub fn completions(command: CompletionsCommand) -> Result<()> {
+    let mut cmd = super::super::fuelup_cli::Cli::command();
+    let bin_name = cmd.get_name().to_string();
+    generate(command.shell, &mut cmd, bin_name, &mut std::io::stdout());
+    Ok(())
+}
diff --git a/src/ops/mod.rs b/src/ops/mod.rs
index b73f57f2..27c3cfb8 100644
--- a/src/ops/mod.rs
+++ b/src/ops/mod.rs
@@ -1,4 +1,5 @@
 pub mod fuelup_check;
+pub mod fuelup_completions;
 pub mod fuelup_component;
 pub mod fuelup_default;
 pub mod fuelup_self;


Test patch:
diff --git a/tests/commands.rs b/tests/commands.rs
index 1f99591c..86dc0514 100644
--- a/tests/commands.rs
+++ b/tests/commands.rs
@@ -536,3 +536,17 @@ You may create a custom toolchain using 'fuelup toolchain new <toolchain>'.
     })?;
     Ok(())
 }
+
+#[test]
+fn fuelup_completions() -> Result<()> {
+    testcfg::setup(FuelupState::LatestToolchainInstalled, &|cfg| {
+        let shells = ["zsh", "bash", "fish", "powershell", "elvish"];
+        for shell in shells {
+            let output = cfg.fuelup(&["completions", "--shell", shell]);
+
+            assert!(output.status.success());
+        }
+    })?;
+
+    Ok(())
+}