Skip to content

Commit d2eb1b6

Browse files
tnk4oncgwalters
authored andcommitted
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 0ee11db commit d2eb1b6

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;
@@ -745,6 +746,15 @@ pub(crate) enum Opt {
745746
/// Diff current /etc configuration versus default
746747
#[clap(hide = true)]
747748
ConfigDiff,
749+
/// Generate shell completion script for supported shells.
750+
///
751+
/// Example: `bootc completion bash` prints a bash completion script to stdout.
752+
#[clap(hide = true)]
753+
Completion {
754+
/// Shell type to generate (bash, zsh, fish)
755+
#[clap(value_enum)]
756+
shell: clap_complete::aot::Shell,
757+
},
748758
#[clap(hide = true)]
749759
DeleteDeployment {
750760
depl_id: String,
@@ -1582,6 +1592,15 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
15821592
Ok(())
15831593
}
15841594
},
1595+
Opt::Completion { shell } => {
1596+
use clap_complete::aot::generate;
1597+
1598+
let mut cmd = Opt::command();
1599+
let mut stdout = std::io::stdout();
1600+
let bin_name = "bootc";
1601+
generate(shell, &mut cmd, bin_name, &mut stdout);
1602+
Ok(())
1603+
}
15851604
Opt::Image(opts) => match opts {
15861605
ImageOpts::List {
15871606
list_type,
@@ -2012,4 +2031,29 @@ mod tests {
20122031
]));
20132032
assert_eq!(args.as_slice(), ["container", "image", "pull"]);
20142033
}
2034+
2035+
#[test]
2036+
fn test_generate_completion_scripts_contain_commands() {
2037+
use clap_complete::aot::{generate, Shell};
2038+
2039+
// For each supported shell, generate the completion script and
2040+
// ensure obvious subcommands appear in the output. This mirrors
2041+
// the style of completion checks used in other projects (e.g.
2042+
// podman) where the generated script is examined for expected
2043+
// tokens.
2044+
2045+
// `completion` is intentionally hidden from --help / suggestions;
2046+
// ensure other visible subcommands are present instead.
2047+
let want = ["install", "upgrade"];
2048+
2049+
for shell in [Shell::Bash, Shell::Zsh, Shell::Fish] {
2050+
let mut cmd = Opt::command();
2051+
let mut buf = Vec::new();
2052+
generate(shell, &mut cmd, "bootc", &mut buf);
2053+
let s = String::from_utf8(buf).expect("completion should be utf8");
2054+
for w in &want {
2055+
assert!(s.contains(w), "{shell:?} completion missing {w}");
2056+
}
2057+
}
2058+
}
20152059
}

0 commit comments

Comments
 (0)