1 //! Shell support 2 3 mod bash; 4 mod fish; 5 mod shell; 6 7 pub use bash::*; 8 pub use fish::*; 9 pub use shell::*; 10 11 use std::ffi::OsString; 12 use std::io::Write as _; 13 14 use crate::dynamic::Completer as _; 15 16 /// A subcommand definition to `flatten` into your CLI 17 /// 18 /// This provides a one-stop solution for integrating completions into your CLI 19 #[derive(clap::Subcommand)] 20 #[allow(missing_docs)] 21 #[derive(Clone, Debug)] 22 #[command(about = None, long_about = None)] 23 pub enum CompleteCommand { 24 /// Register shell completions for this program 25 #[command(hide = true)] 26 Complete(CompleteArgs), 27 } 28 29 /// Generally used via [`CompleteCommand`] 30 #[derive(clap::Args)] 31 #[command(arg_required_else_help = true)] 32 #[command(group = clap::ArgGroup::new("complete").multiple(true).conflicts_with("register"))] 33 #[allow(missing_docs)] 34 #[derive(Clone, Debug)] 35 #[command(about = None, long_about = None)] 36 pub struct CompleteArgs { 37 /// Specify shell to complete for 38 #[arg(long)] 39 shell: Shell, 40 41 /// Path to write completion-registration to 42 #[arg(long, required = true)] 43 register: Option<std::path::PathBuf>, 44 45 #[arg(raw = true, hide_short_help = true, group = "complete")] 46 comp_words: Vec<OsString>, 47 } 48 49 impl CompleteCommand { 50 /// Process the completion request complete(&self, cmd: &mut clap::Command) -> std::convert::Infallible51 pub fn complete(&self, cmd: &mut clap::Command) -> std::convert::Infallible { 52 self.try_complete(cmd).unwrap_or_else(|e| e.exit()); 53 std::process::exit(0) 54 } 55 56 /// Process the completion request try_complete(&self, cmd: &mut clap::Command) -> clap::error::Result<()>57 pub fn try_complete(&self, cmd: &mut clap::Command) -> clap::error::Result<()> { 58 debug!("CompleteCommand::try_complete: {self:?}"); 59 let CompleteCommand::Complete(args) = self; 60 if let Some(out_path) = args.register.as_deref() { 61 let mut buf = Vec::new(); 62 let name = cmd.get_name(); 63 let bin = cmd.get_bin_name().unwrap_or_else(|| cmd.get_name()); 64 args.shell.write_registration(name, bin, bin, &mut buf)?; 65 if out_path == std::path::Path::new("-") { 66 std::io::stdout().write_all(&buf)?; 67 } else if out_path.is_dir() { 68 let out_path = out_path.join(args.shell.file_name(name)); 69 std::fs::write(out_path, buf)?; 70 } else { 71 std::fs::write(out_path, buf)?; 72 } 73 } else { 74 let current_dir = std::env::current_dir().ok(); 75 76 let mut buf = Vec::new(); 77 args.shell.write_complete( 78 cmd, 79 args.comp_words.clone(), 80 current_dir.as_deref(), 81 &mut buf, 82 )?; 83 std::io::stdout().write_all(&buf)?; 84 } 85 86 Ok(()) 87 } 88 } 89