diff --git a/Cargo.lock b/Cargo.lock index ec93621f8..93c619840 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -369,6 +369,15 @@ dependencies = [ "terminal_size", ] +[[package]] +name = "clap_complete" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c9f1dde76b736e3681f28cec9d5a61299cbaae0fce80a68e43724ad56031eb" +dependencies = [ + "clap", +] + [[package]] name = "clap_derive" version = "4.5.32" @@ -2883,6 +2892,7 @@ dependencies = [ "anyhow", "ariadne", "clap", + "clap_complete", "regex", "rustyline", "xee-interpreter", diff --git a/xee/Cargo.toml b/xee/Cargo.toml index 0f6ef87e9..7f3cb62c0 100644 --- a/xee/Cargo.toml +++ b/xee/Cargo.toml @@ -17,6 +17,7 @@ xee-xslt-compiler = { path = "../xee-xslt-compiler", version = "0.1.6" } xee-interpreter = { path = "../xee-interpreter", version = "0.2.0" } xot = { workspace = true } clap = { workspace = true, features = ["derive", "cargo"] } +clap_complete = "4.6.0" ariadne = "0.5.1" anyhow.workspace = true regex = { workspace = true } diff --git a/xee/src/completion.rs b/xee/src/completion.rs new file mode 100644 index 000000000..b149064d3 --- /dev/null +++ b/xee/src/completion.rs @@ -0,0 +1,48 @@ +use std::io; + +use clap::{CommandFactory, Parser}; +use clap_complete::Shell; + +use crate::Cli; + +/// Generate shell completion scripts. +/// +/// The completion script is written to stdout. +#[derive(Debug, Parser)] +pub(crate) struct Completion { + /// The shell to generate completions for. + pub(crate) shell: Shell, +} + +impl Completion { + pub(crate) fn run(&self) -> anyhow::Result<()> { + let mut cmd = Cli::command(); + clap_complete::generate(self.shell, &mut cmd, "xee", &mut io::stdout()); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use clap::ValueEnum; + + use super::*; + + #[test] + fn verify_cli_for_completion() { + Cli::command().debug_assert(); + } + + #[test] + fn generate_completions_for_all_shells() { + for shell in Shell::value_variants() { + let mut cmd = Cli::command(); + let mut buf = Vec::new(); + clap_complete::generate(*shell, &mut cmd, "xee", &mut buf); + assert!( + !buf.is_empty(), + "completion output for {shell} should not be empty" + ); + } + } +} diff --git a/xee/src/main.rs b/xee/src/main.rs index 038567820..e7fce94b5 100644 --- a/xee/src/main.rs +++ b/xee/src/main.rs @@ -1,4 +1,5 @@ mod common; +mod completion; mod error; mod format; mod indent; @@ -32,11 +33,16 @@ enum Commands { Repl(repl::Repl), /// Transform an XML document using an XSLT stylesheet. Xslt(xslt::Xslt), + /// Generate shell completion scripts. + Completion(completion::Completion), } fn main() -> anyhow::Result<()> { let cli = Cli::parse(); match cli.command { + Commands::Completion(completion) => { + completion.run()?; + } Commands::Indent(indent) => { indent.run()?; }