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