-
Notifications
You must be signed in to change notification settings - Fork 166
cli: Add shell completion generation command #1881
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ use anyhow::{anyhow, ensure, Context, Result}; | |
| use camino::{Utf8Path, Utf8PathBuf}; | ||
| use cap_std_ext::cap_std; | ||
| use cap_std_ext::cap_std::fs::Dir; | ||
| use clap::CommandFactory; | ||
| use clap::Parser; | ||
| use clap::ValueEnum; | ||
| use composefs::dumpfile; | ||
|
|
@@ -417,6 +418,15 @@ pub(crate) enum ImageCmdOpts { | |
| }, | ||
| } | ||
|
|
||
| /// Supported completion shells | ||
| #[derive(Debug, Clone, ValueEnum, PartialEq, Eq)] | ||
| #[clap(rename_all = "lowercase")] | ||
| pub(crate) enum CompletionShell { | ||
| Bash, | ||
| Zsh, | ||
| Fish, | ||
| } | ||
|
|
||
| #[derive(ValueEnum, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] | ||
| #[serde(rename_all = "kebab-case")] | ||
| pub(crate) enum ImageListType { | ||
|
|
@@ -744,6 +754,15 @@ pub(crate) enum Opt { | |
| /// Diff current /etc configuration versus default | ||
| #[clap(hide = true)] | ||
| ConfigDiff, | ||
| /// Generate shell completion script for supported shells. | ||
| /// | ||
| /// Example: `bootc completion bash` prints a bash completion script to stdout. | ||
| #[clap(hide = true)] | ||
| Completion { | ||
| /// Shell type to generate (bash, zsh, fish) | ||
| #[clap(value_enum)] | ||
| shell: CompletionShell, | ||
| }, | ||
| #[clap(hide = true)] | ||
| DeleteDeployment { | ||
| depl_id: String, | ||
|
|
@@ -1581,6 +1600,20 @@ async fn run_from_opt(opt: Opt) -> Result<()> { | |
| Ok(()) | ||
| } | ||
| }, | ||
| Opt::Completion { shell } => { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like a totally different option is to generate this data at compile time, per https://docs.rs/clap_complete/latest/clap_complete/aot/fn.generate_to.html I don't have a strong sense of the tradeoffs - if this doesn't notably increase the binary size, then doing it dynamically seems completely fine.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've kept the runtime generation approach primarily for two reasons:
If you think compile-time generation would be better for this use case, I can refactor it. What are your thoughts? |
||
| use clap_complete::aot::{generate, Shell}; | ||
|
|
||
| let mut cmd = Opt::command(); | ||
| let mut stdout = std::io::stdout(); | ||
| let bin_name = "bootc"; | ||
| let shell_generator = match shell { | ||
| CompletionShell::Bash => Shell::Bash, | ||
| CompletionShell::Zsh => Shell::Zsh, | ||
| CompletionShell::Fish => Shell::Fish, | ||
| }; | ||
| generate(shell_generator, &mut cmd, bin_name, &mut stdout); | ||
| Ok(()) | ||
| } | ||
| Opt::Image(opts) => match opts { | ||
| ImageOpts::List { | ||
| list_type, | ||
|
|
@@ -1986,4 +2019,29 @@ mod tests { | |
| ])); | ||
| assert_eq!(args.as_slice(), ["container", "image", "pull"]); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_generate_completion_scripts_contain_commands() { | ||
| use clap_complete::aot::{generate, Shell}; | ||
|
|
||
| // For each supported shell, generate the completion script and | ||
| // ensure obvious subcommands appear in the output. This mirrors | ||
| // the style of completion checks used in other projects (e.g. | ||
| // podman) where the generated script is examined for expected | ||
| // tokens. | ||
|
|
||
| // `completion` is intentionally hidden from --help / suggestions; | ||
| // ensure other visible subcommands are present instead. | ||
| let want = ["install", "upgrade"]; | ||
|
|
||
| for shell in [Shell::Bash, Shell::Zsh, Shell::Fish] { | ||
| let mut cmd = Opt::command(); | ||
| let mut buf = Vec::new(); | ||
| generate(shell, &mut cmd, "bootc", &mut buf); | ||
| let s = String::from_utf8(buf).expect("completion should be utf8"); | ||
| for w in &want { | ||
| assert!(s.contains(w), "{shell:?} completion missing {w}"); | ||
| } | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we just directly use https://docs.rs/clap_complete/latest/clap_complete/aot/enum.Shell.html and avoid a custom enum?