diff --git a/complete/src/bash.rs b/complete/src/bash.rs new file mode 100644 index 0000000..265c2c0 --- /dev/null +++ b/complete/src/bash.rs @@ -0,0 +1,84 @@ +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use crate::{Command, Flag}; + +/// Create completion script for `bash` +/// +/// Short and long options are combined into single `complete` calls, even if +/// they differ in whether they take arguments or not; just like in case of `fish`. +/// Also, pretend that files are fine in any position. ValueHints are ignored entirely. +pub fn render(c: &Command) -> String { + let mut out = String::new(); + // Be careful around the program '['! + let name_identifier = if c.name == "[" { &"bracket" } else { &c.name }; + // Register _comp_uu_FOO as a bash function that computes completions: + out.push_str(&format!( + "complete -F _comp_uu_{name_identifier} '{}';", + &c.name + )); + out.push_str(&format!("_comp_uu_{name_identifier}()")); + // Unless the current argument starts with "-", pre-populate the completions list with all files and dirs: + out.push_str("{ local cur;_init_completion||return;COMPREPLY=();if [[ \"$cur\" != \"-*\" ]]; then _filedir;fi;COMPREPLY+=($(compgen -W \""); + for arg in &c.args { + for Flag { flag, .. } in &arg.short { + out.push_str(&format!("-{flag} ")); + } + for Flag { flag, .. } in &arg.long { + out.push_str(&format!("--{flag} ")); + } + } + out.push_str("\" -- \"$cur\"));}\n"); + out +} + +#[cfg(test)] +mod test { + use super::render; + use crate::{Arg, Command, Flag, Value}; + + #[test] + fn simple() { + let c = Command { + name: "foo", + args: vec![ + Arg { + short: vec![Flag { + flag: "a", + value: Value::No, + }], + long: vec![Flag { + flag: "all", + value: Value::No, + }], + ..Arg::default() + }, + Arg { + short: vec![Flag { + flag: "x", + value: Value::No, + }], + ..Arg::default() + }, + ], + ..Command::default() + }; + assert_eq!(render(&c), "complete -F _comp_uu_foo 'foo';_comp_uu_foo(){ local cur;_init_completion||return;COMPREPLY=();if [[ \"$cur\" != \"-*\" ]]; then _filedir;fi;COMPREPLY+=($(compgen -W \"-a --all -x \" -- \"$cur\"));}\n") + } + + #[test] + fn bracket() { + let c = Command { + name: "[", + args: vec![Arg { + short: vec![Flag { + flag: "x", + value: Value::No, + }], + ..Arg::default() + }], + ..Command::default() + }; + assert_eq!(render(&c), "complete -F _comp_uu_bracket '[';_comp_uu_bracket(){ local cur;_init_completion||return;COMPREPLY=();if [[ \"$cur\" != \"-*\" ]]; then _filedir;fi;COMPREPLY+=($(compgen -W \"-x \" -- \"$cur\"));}\n") + } +} diff --git a/complete/src/lib.rs b/complete/src/lib.rs index 5d8649c..39cbd62 100644 --- a/complete/src/lib.rs +++ b/complete/src/lib.rs @@ -13,6 +13,7 @@ //! - Some information is removed because it is irrelevant for completion and documentation //! - This struct is meant to exist at runtime of the program //! +mod bash; mod fish; mod man; mod md; @@ -75,7 +76,8 @@ pub fn render(c: &Command, shell: &str) -> String { "zsh" => zsh::render(c), "nu" | "nushell" => nu::render(c), "man" => man::render(c), - "sh" | "bash" | "csh" | "elvish" | "powershell" => panic!("shell '{shell}' completion is not implemented yet!"), - _ => panic!("unknown option '{shell}'! Expected one of: \"md\", \"fish\", \"zsh\", \"man\", \"sh\", \"bash\", \"csh\", \"elvish\", \"powershell\""), + "bash" => bash::render(c), + "sh" | "csh" | "elvish" | "powershell" => panic!("shell '{shell}' completion is not implemented yet!"), + _ => panic!("unknown option '{shell}'! Expected one of: \"md\", \"fish\", \"zsh\", \"nu[shell]\", \"man\", \"sh\", \"bash\", \"csh\", \"elvish\", \"powershell\""), } } diff --git a/docs/guide/completions.md b/docs/guide/completions.md index f325b91..d89d3dd 100644 --- a/docs/guide/completions.md +++ b/docs/guide/completions.md @@ -33,7 +33,7 @@ Shell completions and documentation can be generated automatically by this crate cargo run --features parse-is-complete -- [shell] ``` -The `[shell]` value here can be `fish`, `zsh`, `bash`, `powershell`, `elvish` or `nu`. +The `[shell]` value here can be `fish`, `zsh`, `nu`, `sh`, `bash`, `csh`, `elvish`, or `powershell`. > **Note**: Some of these remain unimplemented as of writing. @@ -47,4 +47,4 @@ If you do not want to hijack the [`Options::parse`](crate::Options::parse) funct [Up](super) [Next]() - \ No newline at end of file +