Skip to content

Commit 0211fe2

Browse files
committed
cli: Add shell completion generation command
- Add completion subcommand supporting bash, zsh, and fish Assisted-by: Cursor (Auto) Signed-off-by: Shion Tanaka <shtanaka@redhat.com>
1 parent 0aae35a commit 0211fe2

3 files changed

Lines changed: 55 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/lib/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ cap-std-ext = { workspace = true, features = ["fs_utf8"] }
3333
cfg-if = { workspace = true }
3434
chrono = { workspace = true, features = ["serde"] }
3535
clap = { workspace = true, features = ["derive","cargo"] }
36+
clap_complete = "4"
3637
clap_mangen = { workspace = true, optional = true }
3738
composefs = { workspace = true }
3839
composefs-boot = { workspace = true }

crates/lib/src/cli.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use anyhow::{anyhow, ensure, Context, Result};
1212
use camino::{Utf8Path, Utf8PathBuf};
1313
use cap_std_ext::cap_std;
1414
use cap_std_ext::cap_std::fs::Dir;
15+
use clap::CommandFactory;
1516
use clap::Parser;
1617
use clap::ValueEnum;
1718
use composefs::dumpfile;
@@ -733,6 +734,15 @@ pub(crate) enum Opt {
733734
/// Diff current /etc configuration versus default
734735
#[clap(hide = true)]
735736
ConfigDiff,
737+
/// Generate shell completion script for supported shells.
738+
///
739+
/// Example: `bootc completion bash` prints a bash completion script to stdout.
740+
#[clap(hide = true)]
741+
Completion {
742+
/// Shell type to generate (bash, zsh, fish)
743+
#[clap(value_enum)]
744+
shell: clap_complete::aot::Shell,
745+
},
736746
#[clap(hide = true)]
737747
DeleteDeployment {
738748
depl_id: String,
@@ -1573,6 +1583,15 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
15731583
Ok(())
15741584
}
15751585
},
1586+
Opt::Completion { shell } => {
1587+
use clap_complete::aot::generate;
1588+
1589+
let mut cmd = Opt::command();
1590+
let mut stdout = std::io::stdout();
1591+
let bin_name = "bootc";
1592+
generate(shell, &mut cmd, bin_name, &mut stdout);
1593+
Ok(())
1594+
}
15761595
Opt::Image(opts) => match opts {
15771596
ImageOpts::List {
15781597
list_type,
@@ -1978,4 +1997,29 @@ mod tests {
19781997
]));
19791998
assert_eq!(args.as_slice(), ["container", "image", "pull"]);
19801999
}
2000+
2001+
#[test]
2002+
fn test_generate_completion_scripts_contain_commands() {
2003+
use clap_complete::aot::{generate, Shell};
2004+
2005+
// For each supported shell, generate the completion script and
2006+
// ensure obvious subcommands appear in the output. This mirrors
2007+
// the style of completion checks used in other projects (e.g.
2008+
// podman) where the generated script is examined for expected
2009+
// tokens.
2010+
2011+
// `completion` is intentionally hidden from --help / suggestions;
2012+
// ensure other visible subcommands are present instead.
2013+
let want = ["install", "upgrade"];
2014+
2015+
for shell in [Shell::Bash, Shell::Zsh, Shell::Fish] {
2016+
let mut cmd = Opt::command();
2017+
let mut buf = Vec::new();
2018+
generate(shell, &mut cmd, "bootc", &mut buf);
2019+
let s = String::from_utf8(buf).expect("completion should be utf8");
2020+
for w in &want {
2021+
assert!(s.contains(w), "{shell:?} completion missing {w}");
2022+
}
2023+
}
2024+
}
19812025
}

0 commit comments

Comments
 (0)