Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ cap-std-ext = { workspace = true, features = ["fs_utf8"] }
cfg-if = { workspace = true }
chrono = { workspace = true, features = ["serde"] }
clap = { workspace = true, features = ["derive","cargo"] }
clap_complete = "4"
clap_mangen = { workspace = true, optional = true }
composefs = { workspace = true }
composefs-boot = { workspace = true }
Expand Down
83 changes: 83 additions & 0 deletions crates/lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1581,6 +1600,19 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
Ok(())
}
},
Opt::Completion { shell } => {
use clap_complete::{generate, shells};

let mut cmd = Opt::command();
let mut stdout = std::io::stdout();
let bin_name = "bootc";
match shell {
CompletionShell::Bash => generate(shells::Bash, &mut cmd, bin_name, &mut stdout),
CompletionShell::Zsh => generate(shells::Zsh, &mut cmd, bin_name, &mut stdout),
CompletionShell::Fish => generate(shells::Fish, &mut cmd, bin_name, &mut stdout),
};
Ok(())
}
Opt::Image(opts) => match opts {
ImageOpts::List {
list_type,
Expand Down Expand Up @@ -1986,4 +2018,55 @@ mod tests {
]));
assert_eq!(args.as_slice(), ["container", "image", "pull"]);
}

#[test]
fn test_generate_completion_scripts_contain_commands() {
use clap_complete::{
generate,
shells::{Bash, Fish, Zsh},
};

// 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"];

// Bash
{
let mut cmd = Opt::command();
let mut buf = Vec::new();
generate(Bash, &mut cmd, "bootc", &mut buf);
let s = String::from_utf8(buf).expect("bash completion should be utf8");
for w in &want {
assert!(s.contains(w), "bash completion missing {w}");
}
}

// Zsh
{
let mut cmd = Opt::command();
let mut buf = Vec::new();
generate(Zsh, &mut cmd, "bootc", &mut buf);
let s = String::from_utf8(buf).expect("zsh completion should be utf8");
for w in &want {
assert!(s.contains(w), "zsh completion missing {w}");
}
}

// Fish
{
let mut cmd = Opt::command();
let mut buf = Vec::new();
generate(Fish, &mut cmd, "bootc", &mut buf);
let s = String::from_utf8(buf).expect("fish completion should be utf8");
for w in &want {
assert!(s.contains(w), "fish completion missing {w}");
}
}
}
}