Repository: stjude-rust-labs/sprocket
Pull request #105: feat: add support for tab completions
Author: login: dead8309
Created: 2025-05-02T23:24:45Z | Merged: 2025-05-07T15:50:52Z

closes #101 
Before submitting this PR, please make sure:

For external contributors:

- [x] You have read the `wdl` crates' [CONTRIBUTING](https://github.com/stjude-rust-labs/wdl/blob/main/CONTRIBUTING.md) guide in its entirety.
- [x] You have not used AI on any parts of this pull request.

For all contributors:

- [x] You have added a few sentences describing the PR here.
- [x] You have added yourself or the appropriate individual as the assignee.
- [x] You have added at least one relevant code reviewer to the PR.
- [x] Your code builds clean without any errors or warnings.
- [x] You have added tests (when appropriate).
- [x] You have added an entry in the CHANGELOG (when appropriate).
- [x] You have updated the README or other documentation to account for these changes (when appropriate).


Linked issue #101: Feature request: Add shell completions for sprocket
**Is your feature request related to a problem? Please describe.**
shell completions would be a nice given that number of sprocket commands will increase in future (If not then i think it's fine to ignore this)

**Describe the solution you'd like**
`sprocket <TAB>` should auto-complete

**Describe alternatives you've considered**
N/A

**Additional context**
https://crates.io/crates/clap_complete
Linked issue comments: user: login: dead8309
created_at: 2025-04-25T10:33:19Z
body: copied issue template from `stjude-rust-labs/wdl`
author_association: CONTRIBUTOR
user: login: dead8309
created_at: 2025-04-25T10:36:50Z
body: Here's an example of using `clap_complete` for `derive` based cli's

https://github.com/clap-rs/clap/blob/master/clap_complete/examples/completion-derive.rs
author_association: CONTRIBUTOR
user: login: a-frantz
created_at: 2025-04-25T13:07:25Z
body: Seems nice to have! I'd call it far from a need, but in now way opposed to adding this feature.

I would want to test drive it before merging though. I think `clap` is a nicely done crate, so I don't anticipate any issues, but I'm aware of some python tooling with "tab completions" that are buggier than they are useful. I wouldn't want to bring in something that lowers the level of "polish" on Sprocket just to say we have a feature that doesn't work well. (But with my general impression of `clap`, I don't think that's likely)
author_association: MEMBER
user: login: dead8309
created_at: 2025-05-02T19:30:42Z
body: Hi, so when i opened this issue, i was initially thinking about having tab completions for the "Tags" and "Rules". what do you think about that ?
author_association: CONTRIBUTOR

Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c1869db2c..11113c753 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## Unreleased
 
+### Added
+
+* Added tab completions for `sprocket` commands ([#105](https://github.com/stjude-rust-labs/sprocket/pull/105)).
+
 ## 0.12.2 - 05-05-2025
 
 ### Fixed
diff --git a/Cargo.lock b/Cargo.lock
index 606062213..c7cac7ed3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -409,6 +409,15 @@ dependencies = [
  "strsim 0.11.1",
 ]
 
+[[package]]
+name = "clap_complete"
+version = "4.5.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be8c97f3a6f02b9e24cadc12aaba75201d18754b53ea0a9d99642f806ccdb4c9"
+dependencies = [
+ "clap",
+]
+
 [[package]]
 name = "clap_derive"
 version = "4.5.32"
@@ -3189,6 +3198,7 @@ dependencies = [
  "chrono",
  "clap",
  "clap-verbosity-flag",
+ "clap_complete",
  "codespan-reporting",
  "colored",
  "futures",
diff --git a/Cargo.toml b/Cargo.toml
index 4c8078d55..8d6a1cf22 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,6 +15,7 @@ anyhow = "1.0.91"
 chrono = "0.4.41"
 clap = { version = "4.5.37", features = ["derive", "string"] }
 clap-verbosity-flag = { version = "3.0.2", features = ["tracing"] }
+clap_complete = "4.5.48"
 codespan-reporting = "0.12.0"
 colored = "3.0.0"
 futures = "0.3.31"
diff --git a/README.md b/README.md
index e3c8b5a8d..ecb303505 100644
--- a/README.md
+++ b/README.md
@@ -37,6 +37,7 @@
 - **`sprocket lint`** performs static analysis on a document or directory of documents with additional linting rules enabled (effectively a shortcut for `check --lint`).
 - **`sprocket run`** runs a task or workflow.
 - **`sprocket validate`** validates a set of inputs read from files or on the command line against a task or workflow.
+- **`sprocket completions`** generates shell completions for Sprocket.
 
 ## Guiding Principles
 
diff --git a/docs/installation.md b/docs/installation.md
index 23a3d5631..e89baac33 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -46,6 +46,59 @@ tool are available on GitHub at
   available on `main`) _or_ any experimental features by checking out the
   associated feature branch (`git checkout <branch-name>`).
 
+## Shell Completions
+
+`sprocket` can generate command-line completion scripts for various shells,
+allowing you to use tab completion for commands and arguments.
+
+::: warning Warning
+The `sprocket` command line tool is currently under active development and is not yet
+considered stable. This means commands, flags, or arguments might change between
+versions. **You will need to regenerate the shell completion script using the
+steps below each time you update `sprocket`**.
+:::
+
+To generate a completion script, use the `completions` subcommand, specifying your shell:
+
+::: code-group
+
+```shell
+sprocket completions <SHELL>
+```
+
+:::
+
+Supported shells are: `bash`, `elvish`, `fish`, `powershell` and `zsh`.
+
+### Enabling Completions
+
+The exact steps to correctly enable shell completions depend on your specific
+shell and how it's configured. Generally it involves two main steps:
+
+1. Run the `sprocket completions <your shell>` command and redirect its standard output into a file,
+   often somewhere in your home directory. For example, a Bash user might run:
+
+::: code-group
+
+```shell
+sprocket completions bash > ~/.bash_completions/sprocket.bash
+```
+
+:::
+
+2. Modify you shell's startup configuration file (e.g. `~/.bashrc`, `~/.zshrc`,
+`~/.config/fish/config.fish`, PowerShell's `$PROFILE`, Elvish's
+`~/.config/elvish/rc.elv`) to source the file you just created. Continuing the
+Bash example, add this line to your `~/.bashrc`
+
+::: code-group
+
+```shell
+source ~/.bash_completions/sprocket.bash
+```
+
+:::
+
 [Homebrew]: https://brew.sh/
 [Rust]: https://rust-lang.org/
 [rustup]: https://rustup.rs/
diff --git a/src/commands.rs b/src/commands.rs
index 56ab53c28..9aa667079 100644
--- a/src/commands.rs
+++ b/src/commands.rs
@@ -2,6 +2,7 @@
 
 pub mod analyzer;
 pub mod check;
+pub mod completions;
 pub mod explain;
 pub mod format;
 pub mod run;
diff --git a/src/commands/check.rs b/src/commands/check.rs
index b9b4729c6..eedcc2e72 100644
--- a/src/commands/check.rs
+++ b/src/commands/check.rs
@@ -5,6 +5,7 @@ use std::collections::HashSet;
 use anyhow::Context;
 use anyhow::bail;
 use clap::Parser;
+use clap::builder::PossibleValuesParser;
 use codespan_reporting::diagnostic::Diagnostic;
 use codespan_reporting::files::SimpleFiles;
 use tracing::info;
@@ -14,6 +15,7 @@ use wdl::cli::Analysis;
 use wdl::cli::analysis::Source;
 use wdl::lint::find_nearest_rule;
 
+use super::explain::ALL_RULE_IDS;
 use crate::Mode;
 use crate::emit_diagnostics;
 use crate::get_display_config;
@@ -29,7 +31,11 @@ pub struct Common {
     /// Excepts (ignores) an analysis or lint rule.
     ///
     /// Repeat the flag multiple times to except multiple rules.
-    #[clap(short, long, value_name = "RULE")]
+    #[clap(short, long, value_name = "RULE",
+        value_parser = PossibleValuesParser::new(ALL_RULE_IDS.iter()),
+        action = clap::ArgAction::Append,
+        num_args = 1,
+    )]
     pub except: Vec<String>,
 
     /// Causes the command to fail if warnings were reported.
diff --git a/src/commands/completions.rs b/src/commands/completions.rs
new file mode 100644
index 000000000..c9381c373
--- /dev/null
+++ b/src/commands/completions.rs
@@ -0,0 +1,29 @@
+//! Implementation of the `completions` subcommand.
+
+use std::io;
+
+use anyhow::Result;
+use clap::Command;
+use clap::Parser;
+use clap_complete::Shell;
+use clap_complete::generate;
+
+/// Arguments for the `completions` subcommand.
+#[derive(Parser, Debug)]
+#[command(author, version, about)]
+pub struct Args {
+    /// The shell to generate completions for.
+    #[arg(value_enum)]
+    shell: Shell,
+}
+
+/// The main function for the `completions` subcommand.
+pub async fn completions(args: Args, cmd: &mut Command) -> Result<()> {
+    generate(
+        args.shell,
+        cmd,
+        cmd.get_name().to_string(),
+        &mut io::stdout(),
+    );
+    Ok(())
+}
diff --git a/src/commands/explain.rs b/src/commands/explain.rs
index ee4612fd9..4d16d079f 100644
--- a/src/commands/explain.rs
+++ b/src/commands/explain.rs
@@ -1,11 +1,13 @@
 //! Implementation of the `explain` subcommand.
 
 use std::collections::HashSet;
+use std::sync::LazyLock;
 
 use anyhow::Ok;
 use anyhow::anyhow;
 use anyhow::bail;
 use clap::Parser;
+use clap::builder::PossibleValuesParser;
 use colored::Colorize;
 use wdl::analysis;
 use wdl::lint;
@@ -16,6 +18,30 @@ const USAGE: &str = "sprocket explain [RULE]
     sprocket explain --tag <TAG>
     sprocket explain --definitions";
 
+/// All rule IDs sorted alphabetically.
+pub static ALL_RULE_IDS: LazyLock<Vec<String>> = LazyLock::new(|| {
+    let mut ids: Vec<String> = analysis::rules()
+        .iter()
+        .map(|r| r.id().to_string())
+        .collect();
+    ids.extend(lint::rules().iter().map(|r| r.id().to_string()));
+    ids.sort();
+    ids
+});
+
+/// All tag names sorted alphabetically.
+static ALL_TAG_NAMES: LazyLock<Vec<String>> = LazyLock::new(|| {
+    let mut tags: HashSet<Tag> = HashSet::new();
+    for rule in lint::rules() {
+        for tag in rule.tags().iter() {
+            tags.insert(tag);
+        }
+    }
+    let mut tag_names: Vec<String> = tags.into_iter().map(|t| t.to_string()).collect();
+    tag_names.sort();
+    tag_names
+});
+
 /// Arguments for the `explain` subcommand.
 #[derive(Parser, Debug)]
 #[command(author, version, about, after_help = generate_after_help(), override_usage = USAGE)]
@@ -26,11 +52,17 @@ pub struct Args {
         "definitions",
         "list_all_rules",
         "list_all_tags"
-    ], value_name = "RULE")]
+    ],
+        value_name = "RULE",
+        value_parser = PossibleValuesParser::new(ALL_RULE_IDS.iter())
+    )]
     pub rule_name: Option<String>,
 
     /// List all rules with the given tag.
-    #[arg(short, long, value_name = "TAG", conflicts_with_all = ["rule_name", "definitions"])]
+    #[arg(short, long, value_name = "TAG",
+        conflicts_with_all = ["rule_name", "definitions"],
+        value_parser = PossibleValuesParser::new(ALL_TAG_NAMES.iter())
+    )]
     pub tag: Option<String>,
 
     /// Display general WDL definitions.
@@ -54,47 +86,20 @@ fn generate_after_help() -> String {
 /// Lists all rules as a string for displaying.
 pub fn list_all_rules() -> String {
     let mut result = String::from("Available rules:");
-    let analysis_rules = analysis::rules();
-    let lint_rules = lint::rules();
-
-    let mut indexes = (0..(analysis_rules.len() + lint_rules.len())).collect::<Vec<_>>();
-
-    let id = |index: usize| {
-        if index >= analysis_rules.len() {
-            lint_rules[index - analysis_rules.len()].id()
-        } else {
-            analysis_rules[index].id()
-        }
-    };
 
-    indexes.sort_by(|a, b| id(*a).cmp(id(*b)));
-
-    for index in indexes {
-        result.push_str(&format!("\n  - {}", id(index)));
+    for id in ALL_RULE_IDS.iter() {
+        result.push_str(&format!("\n  - {}", id));
     }
-
     result
 }
 
 /// Lists all tags as a string for displaying.
 pub fn list_all_tags() -> String {
     let mut result = String::from("Available tags:");
-    let lint_rules = lint::rules();
-
-    let mut tags: HashSet<Tag> = HashSet::new();
-    for rule in lint_rules {
-        for tag in rule.tags().iter() {
-            tags.insert(tag);
-        }
-    }
 
-    let mut tags: Vec<Tag> = tags.into_iter().collect();
-    tags.sort_by_key(|tag| tag.to_string());
-
-    for tag in tags {
+    for tag in ALL_TAG_NAMES.iter() {
         result.push_str(&format!("\n  - {}", tag));
     }
-
     result
 }
 
diff --git a/src/main.rs b/src/main.rs
index f89d072aa..f4a0274b1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,6 +3,7 @@
 use std::io::IsTerminal;
 use std::io::stderr;
 
+use clap::CommandFactory;
 use clap::Parser;
 use clap::Subcommand;
 use clap_verbosity_flag::Verbosity;
@@ -47,6 +48,9 @@ enum Commands {
     /// It will not catch potential runtime errors that may occur when running
     /// the task or workflow.
     Validate(commands::validate::Args),
+
+    /// Generates shell completions.
+    Completions(commands::completions::Args),
 }
 
 #[derive(Parser)]
@@ -97,6 +101,10 @@ pub async fn inner() -> anyhow::Result<()> {
         Commands::Lint(args) => commands::check::lint(args).await,
         Commands::Run(args) => commands::run::run(args).await,
         Commands::Validate(args) => commands::validate::validate(args).await,
+        Commands::Completions(args) => {
+            let mut cmd = Cli::command();
+            commands::completions::completions(args, &mut cmd).await
+        }
     }
 }
 


Reviews:
user: login: dead8309
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-03T15:42:04Z
user: login: a-frantz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-03T16:24:58Z
user: login: dead8309
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-03T17:32:26Z
user: login: a-frantz
body: Awesome! Looks great to me
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-03T17:37:14Z
user: login: a-frantz
body: Just a thought, not to be addressed in this PR.

But I'm about to add a new subcommand to the sprocket bin tentatively named `sprocket dev` and it will be used for experimental and "dev only" commands we need for development on the `wdl` crates. As those are likely going to be unstable even between releases (e.g. it will see a lot of changes on `main` before we stabilize for a release), I'm thinking we probably want to prevent `clap_complete` from generating anything that's under the `dev` namespace.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-05T14:12:58Z
user: login: claymcleod
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T22:50:31Z
user: login: claymcleod
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T22:50:57Z
user: login: claymcleod
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T22:51:15Z
user: login: claymcleod
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T22:51:58Z
user: login: claymcleod
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T22:52:37Z
user: login: claymcleod
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T22:52:56Z
user: login: claymcleod
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T22:54:40Z
user: login: claymcleod
body: Otherwise looks good.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-06T22:55:07Z
user: login: a-frantz
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-07T15:32:45Z
user: login: adthrasher
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-07T15:49:41Z

Review comments:
diff_hunk: @@ -74,6 +75,30 @@ Sprocket is available as a Docker [image](https://github.com/stjude-rust-labs/sp
 docker pull ghcr.io/stjude-rust-labs/sprocket:v0.12.0
 ```
 
+#### Shell Completions
+
+Supported shells are: `bash`,`elvish`, `fish`, `powershell` and `zsh`.
+
+```shell
+# bash
+sprocket completions bash > ~/.local/share/bash-completion/completions/sprocket
+
+# elvish
+sprocket completions elvish > ~/.config/elvish/sprocket.elv
+
+# fish
+sprocket completions fish > ~/.config/fish/completions/sprocket.fish
+
+# powershell
+sprocket completions powershell > ~\sprocket_completions.ps1
+Add-Content -Path $PROFILE -Value ". ~\sprocket_completions.ps1"
+
+# zsh
+sprocket completions zsh > /usr/local/share/zsh/site-functions/sprocket
+compinit
+```
+
+
path: README.md
user: login: dead8309
body: Should this stay here ?
created_at: 2025-05-03T15:42:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,6 +75,30 @@ Sprocket is available as a Docker [image](https://github.com/stjude-rust-labs/sp
 docker pull ghcr.io/stjude-rust-labs/sprocket:v0.12.0
 ```
 
+#### Shell Completions
+
+Supported shells are: `bash`,`elvish`, `fish`, `powershell` and `zsh`.
+
+```shell
+# bash
+sprocket completions bash > ~/.local/share/bash-completion/completions/sprocket
+
+# elvish
+sprocket completions elvish > ~/.config/elvish/sprocket.elv
+
+# fish
+sprocket completions fish > ~/.config/fish/completions/sprocket.fish
+
+# powershell
+sprocket completions powershell > ~\sprocket_completions.ps1
+Add-Content -Path $PROFILE -Value ". ~\sprocket_completions.ps1"
+
+# zsh
+sprocket completions zsh > /usr/local/share/zsh/site-functions/sprocket
+compinit
+```
+
+
path: README.md
user: login: a-frantz
body: no, we only want this section once
created_at: 2025-05-03T16:06:24Z
author_association: MEMBER
diff_hunk: @@ -46,6 +46,54 @@ tool are available on GitHub at
   available on `main`) _or_ any experimental features by checking out the
   associated feature branch (`git checkout <branch-name>`).
 
+## Shell Completions
+
+`sprocket` can generate command line completion script for various shells,
path: docs/installation.md
user: login: a-frantz
body: ```suggestion
`sprocket` can generate command a line completion script for various shells,
```
created_at: 2025-05-03T16:06:41Z
author_association: MEMBER
diff_hunk: @@ -46,6 +46,54 @@ tool are available on GitHub at
   available on `main`) _or_ any experimental features by checking out the
   associated feature branch (`git checkout <branch-name>`).
 
+## Shell Completions
+
+`sprocket` can generate command line completion script for various shells,
+allowing you to use tab completion for commands and arguments.
+
+::: warning Warning
+
+`sprocket` cli is currently under active development and is not yet
+considered stable. This means commands, flags, or arguments might change between
+versions. **You will need to regenerate the shell completion script using the
+steps below each time you update `sprocket`**.
+:::
+
+To generate a completion script, use the `completions` subcommand, specifying you shell:
path: docs/installation.md
user: login: a-frantz
body: ```suggestion
To generate a completion script, use the `completions` subcommand, specifying your shell:
```
created_at: 2025-05-03T16:07:09Z
author_association: MEMBER
diff_hunk: @@ -46,6 +46,54 @@ tool are available on GitHub at
   available on `main`) _or_ any experimental features by checking out the
   associated feature branch (`git checkout <branch-name>`).
 
+## Shell Completions
+
+`sprocket` can generate command line completion script for various shells,
+allowing you to use tab completion for commands and arguments.
+
+::: warning Warning
+
+`sprocket` cli is currently under active development and is not yet
+considered stable. This means commands, flags, or arguments might change between
+versions. **You will need to regenerate the shell completion script using the
+steps below each time you update `sprocket`**.
+:::
+
+To generate a completion script, use the `completions` subcommand, specifying you shell:
+
+::: code-group
+
+```shell
+sprocket completions <SHELL>
+```
+
+:::
+
+Supported shells are: `bash`,`elvish`, `fish`, `powershell` and `zsh`.
+
+::: code-group
+
+```shell
+# bash
+sprocket completions bash > ~/.local/share/bash-completion/completions/sprocket
+
+# elvish
+sprocket completions elvish > ~/.config/elvish/sprocket.elv
+
+# fish
+sprocket completions fish > ~/.config/fish/completions/sprocket.fish
+
+# powershell
+sprocket completions powershell > ~\sprocket_completions.ps1
+Add-Content -Path $PROFILE -Value ". ~\sprocket_completions.ps1"
+
+# zsh
+sprocket completions zsh > /usr/local/share/zsh/site-functions/sprocket
+compinit
+```
path: docs/installation.md
user: login: a-frantz
body: I think we should probably be less specific with the file locations. Not everyone's shells are going to be configured to look in the same place, and the above directories may not exist which would cause the redirect to fail.

Something like:

> The exact steps to correctly enable shell completions will be dependent on how your shell is configured. Generally, you will want to redirect the STDOUT of `sprocket completions [your shell]` into a file somewhere in your home directory. For example, a Bash user may use `sprocket completions bash > ~/.bash_completions/sprocket.bash`.
>
> Then you will have to edit your shells startup script to "source" the newly created file and re-initiate your shell. The Bash user from before might use `echo '. ~./bash_completions/sprocket.bash' >> ~/.bashrc && . ~/.bashrc`
created_at: 2025-05-03T16:24:34Z
author_association: MEMBER
diff_hunk: @@ -46,6 +46,54 @@ tool are available on GitHub at
   available on `main`) _or_ any experimental features by checking out the
   associated feature branch (`git checkout <branch-name>`).
 
+## Shell Completions
+
+`sprocket` can generate command line completion script for various shells,
+allowing you to use tab completion for commands and arguments.
+
+::: warning Warning
+
+`sprocket` cli is currently under active development and is not yet
+considered stable. This means commands, flags, or arguments might change between
+versions. **You will need to regenerate the shell completion script using the
+steps below each time you update `sprocket`**.
+:::
+
+To generate a completion script, use the `completions` subcommand, specifying you shell:
+
+::: code-group
+
+```shell
+sprocket completions <SHELL>
+```
+
+:::
+
+Supported shells are: `bash`,`elvish`, `fish`, `powershell` and `zsh`.
+
+::: code-group
+
+```shell
+# bash
+sprocket completions bash > ~/.local/share/bash-completion/completions/sprocket
+
+# elvish
+sprocket completions elvish > ~/.config/elvish/sprocket.elv
+
+# fish
+sprocket completions fish > ~/.config/fish/completions/sprocket.fish
+
+# powershell
+sprocket completions powershell > ~\sprocket_completions.ps1
+Add-Content -Path $PROFILE -Value ". ~\sprocket_completions.ps1"
+
+# zsh
+sprocket completions zsh > /usr/local/share/zsh/site-functions/sprocket
+compinit
+```
path: docs/installation.md
user: login: dead8309
body: ![image](https://github.com/user-attachments/assets/da60ab99-d917-4ade-a2e7-017cd9455e9d)

created_at: 2025-05-03T17:32:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -46,6 +46,59 @@ tool are available on GitHub at
   available on `main`) _or_ any experimental features by checking out the
   associated feature branch (`git checkout <branch-name>`).
 
+## Shell Completions
+
+`sprocket` can generate commandline completion script for various shells,
path: docs/installation.md
user: login: a-frantz
body: ```suggestion
`sprocket` can generate command-line completion scripts for various shells,
```
created_at: 2025-05-05T14:07:28Z
author_association: MEMBER
diff_hunk: @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## Unreleased
 
+### Added
+
+* Added TAB completions for `sprocket` commands ([#105](https://github.com/stjude-rust-labs/sprocket/pull/105)).
path: CHANGELOG.md
user: login: claymcleod
body: ```suggestion
* Added tab completions for `sprocket` commands ([#105](https://github.com/stjude-rust-labs/sprocket/pull/105)).
```
created_at: 2025-05-06T22:50:31Z
author_association: MEMBER
diff_hunk: @@ -46,6 +46,59 @@ tool are available on GitHub at
   available on `main`) _or_ any experimental features by checking out the
   associated feature branch (`git checkout <branch-name>`).
 
+## Shell Completions
+
+`sprocket` can generate command-line completion scripts for various shells,
+allowing you to use tab completion for commands and arguments.
+
+::: warning Warning
+`sprocket` cli is currently under active development and is not yet
+considered stable. This means commands, flags, or arguments might change between
+versions. **You will need to regenerate the shell completion script using the
+steps below each time you update `sprocket`**.
+:::
+
+To generate a completion script, use the `completions` subcommand, specifying your shell:
+
+::: code-group
+
+```shell
+sprocket completions <SHELL>
+```
+
+:::
+
+Supported shells are: `bash`,`elvish`, `fish`, `powershell` and `zsh`.
path: docs/installation.md
user: login: claymcleod
body: ```suggestion
Supported shells are: `bash`, `elvish`, `fish`, `powershell` and `zsh`.
```
created_at: 2025-05-06T22:50:57Z
author_association: MEMBER
diff_hunk: @@ -46,6 +46,59 @@ tool are available on GitHub at
   available on `main`) _or_ any experimental features by checking out the
   associated feature branch (`git checkout <branch-name>`).
 
+## Shell Completions
+
+`sprocket` can generate command-line completion scripts for various shells,
+allowing you to use tab completion for commands and arguments.
+
+::: warning Warning
+`sprocket` cli is currently under active development and is not yet
path: docs/installation.md
user: login: claymcleod
body: ```suggestion
The `sprocket` command line tool is currently under active development and is not yet
```
created_at: 2025-05-06T22:51:15Z
author_association: MEMBER
diff_hunk: @@ -46,6 +46,59 @@ tool are available on GitHub at
   available on `main`) _or_ any experimental features by checking out the
   associated feature branch (`git checkout <branch-name>`).
 
+## Shell Completions
+
+`sprocket` can generate command-line completion scripts for various shells,
+allowing you to use tab completion for commands and arguments.
+
+::: warning Warning
+`sprocket` cli is currently under active development and is not yet
+considered stable. This means commands, flags, or arguments might change between
+versions. **You will need to regenerate the shell completion script using the
+steps below each time you update `sprocket`**.
+:::
+
+To generate a completion script, use the `completions` subcommand, specifying your shell:
+
+::: code-group
+
+```shell
+sprocket completions <SHELL>
+```
+
+:::
+
+Supported shells are: `bash`,`elvish`, `fish`, `powershell` and `zsh`.
+
+### Enabling Completions
+
+The exact steps to correctly enable shell completions depend on your specific
+shell and how it's configured. Generally it involves two main steps:
+
+1. Run the `sprocket completions <your shell>` command and redirect its STDOUT into a file,
path: docs/installation.md
user: login: claymcleod
body: ```suggestion
1. Run the `sprocket completions <your shell>` command and redirect its standard output into a file,
```
created_at: 2025-05-06T22:51:58Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,30 @@
+//! Implementation of the `completions` subcommand.
+
+use std::io;
+
+use anyhow::Result;
+use clap::Command;
+use clap::Parser;
+use clap_complete::Shell;
+use clap_complete::generate;
+
+/// Arguments for the `completions` subcommand.
+#[derive(Parser, Debug)]
+#[command(author, version, about)]
+pub struct Args {
+    /// The shell to generate completions for
path: src/commands/completions.rs
user: login: claymcleod
body: ```suggestion
    /// The shell to generate completions for.
```
created_at: 2025-05-06T22:52:37Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,30 @@
+//! Implementation of the `completions` subcommand.
+
+use std::io;
+
+use anyhow::Result;
+use clap::Command;
+use clap::Parser;
+use clap_complete::Shell;
+use clap_complete::generate;
+
+/// Arguments for the `completions` subcommand.
+#[derive(Parser, Debug)]
+#[command(author, version, about)]
+pub struct Args {
+    /// The shell to generate completions for
+    #[arg(value_enum)]
+    shell: Shell,
+}
+
+/// The main function for the `completions` subcommand.
+pub async fn completions(args: Args, cmd: &mut Command) -> Result<()> {
+    eprintln!("generating completion file for {:?}", args.shell);
path: src/commands/completions.rs
user: login: claymcleod
body: I'd prefer removing this.
created_at: 2025-05-06T22:52:55Z
author_association: MEMBER
diff_hunk: @@ -51,50 +58,47 @@ fn generate_after_help() -> String {
     format!("{}\n\n{}", list_all_rules(), list_all_tags())
 }
 
-/// Lists all rules as a string for displaying.
-pub fn list_all_rules() -> String {
-    let mut result = String::from("Available rules:");
-    let analysis_rules = analysis::rules();
-    let lint_rules = lint::rules();
-
-    let mut indexes = (0..(analysis_rules.len() + lint_rules.len())).collect::<Vec<_>>();
+/// Gets all rule IDs sorted alphabetically.
+pub fn all_rule_ids() -> Vec<String> {
+    let mut ids: Vec<String> = analysis::rules()
+        .iter()
+        .map(|r| r.id().to_string())
+        .collect();
+    ids.extend(lint::rules().iter().map(|r| r.id().to_string()));
+    ids.sort();
+    ids
+}
 
-    let id = |index: usize| {
-        if index >= analysis_rules.len() {
-            lint_rules[index - analysis_rules.len()].id()
-        } else {
-            analysis_rules[index].id()
+/// Gets all tag names sorted alphabetically.
+fn all_tag_names() -> Vec<String> {
+    let mut tags: HashSet<Tag> = HashSet::new();
path: src/commands/explain.rs
user: login: claymcleod
body: Can we initialize this only once? We don't need to pay the cost of compiling the list every time we call `all_tag_names()`. Same for `all_rule_ids()`.
created_at: 2025-05-06T22:54:40Z
author_association: MEMBER

PR comments:
user: login: a-frantz
created_at: 2025-05-03T12:13:38Z
body: Very cool! I got it working locally, but that was a due to some guess work on my part without documentation. We'll need to document the steps for doing it in sprocket.bio. Can you add a new section to `docs/installation.md` with instructions?

We should include a warning that the tab completion file will need to be re-generated each time a user updates sprocket because our CLI isn't stable yet.

I'd also like to see the `--generate` flag become a subcommand. Something like `sprocket shell-completions bash >> ~/.bash_completions`. Reasoning is that right now `sprocket` by itself doesn't "do" anything, every invocation requires a subcommand, and I think we should keep that pattern up.

In #101 you mentioned rules? It would be sweet if we could tab complete args to `lint -e`, but we can log that as a future TODO.
author_association: MEMBER
user: login: dead8309
created_at: 2025-05-03T13:06:59Z
body: > Very cool! I got it working locally, but that was a due to some guess work on my part without documentation.

Yea sorry about that i forgot to mention it on the pr. I'll update the installation.md.

~~I'll switch to `clap_autocomplete` which detects the current shell and place the completions file~~

> We should include a warning that the tab completion file will need to be re-generated each time a user updates sprocket because our CLI isn't stable yet.

adding a subcommand to generate ~~and place completions file~~
```sh
sprocket completions bash >> ~/.bash_completions
```

Edit: Looks like `clap_autocomplete` is not being maintained. I think sticking with `clap_complete` is better

author_association: CONTRIBUTOR
user: login: dead8309
created_at: 2025-05-03T15:18:59Z
body: ![image](https://github.com/user-attachments/assets/89c8f90d-72ca-4111-8acf-d36fc20f2c07)

might be slightly off with bash, elvish, fish, powershell
author_association: CONTRIBUTOR
user: login: dead8309
created_at: 2025-05-03T19:21:52Z
body: ![image](https://github.com/user-attachments/assets/e98f5b01-d1f4-468a-8ec6-848cef7d94c4)
![image](https://github.com/user-attachments/assets/2275d448-0731-4828-bdb6-53c9af3dbc4b)

author_association: CONTRIBUTOR
user: login: dead8309
created_at: 2025-05-06T14:02:19Z
body: >  I'm thinking we probably want to prevent clap_complete from generating anything that's under the dev namespace.

I can only think of - maintaining a different command tree for internal commands
*OR*
manual completion generation and a `hide` flag on `Dev` to remove it from even `help` 
author_association: CONTRIBUTOR
user: login: a-frantz
created_at: 2025-05-06T16:30:12Z
body: > > I'm thinking we probably want to prevent clap_complete from generating anything that's under the dev namespace.
> 
> I can only think of - maintaining a different command tree for internal commands _OR_ manual completion generation and a `hide` flag on `Dev` to remove it from even `help`

neither of these sound _good_. Maybe we just deal with the tab completions being a little wonky between releases... After all, the only people it really impacts are the developers and so long as the completions work for the releases I think we can live with it. 🤷‍♀️ 
author_association: MEMBER