Skip to content

Commit 9f62cf7

Browse files
committed
feat(Completions): adds the ability to generate completions to io::Write object
1 parent 691ef58 commit 9f62cf7

File tree

4 files changed

+90
-23
lines changed

4 files changed

+90
-23
lines changed

src/app/mod.rs

+39-1
Original file line numberDiff line numberDiff line change
@@ -978,7 +978,7 @@ impl<'a, 'b> App<'a, 'b> {
978978
/// `get_matches()`, or any of the other normal methods directly after. For example:
979979
///
980980
/// ```ignore
981-
/// src/main.rs
981+
/// // src/main.rs
982982
///
983983
/// mod cli;
984984
///
@@ -1023,6 +1023,44 @@ impl<'a, 'b> App<'a, 'b> {
10231023
self.p.gen_completions(for_shell, out_dir.into());
10241024
}
10251025

1026+
1027+
/// Generate a completions file for a specified shell at runtime. Until `cargo install` can
1028+
/// install extra files like a completion script, this may be used e.g. in a command that
1029+
/// outputs the contents of the completion script, to be redirected into a file by the user.
1030+
///
1031+
/// # Examples
1032+
///
1033+
/// Assuming a separate `cli.rs` like the [example above](./struct.App.html#method.gen_completions),
1034+
/// we can let users generate a completion script using a command:
1035+
///
1036+
/// ```ignore
1037+
/// // src/main.rs
1038+
///
1039+
/// mod cli;
1040+
/// use std::io;
1041+
///
1042+
/// fn main() {
1043+
/// let matches = cli::build_cli().get_matches();
1044+
///
1045+
/// if matches.is_present("generate-bash-completions") {
1046+
/// cli::build_cli().gen_completions_to("myapp", Shell::Bash, &mut io::stdout());
1047+
/// }
1048+
///
1049+
/// // normal logic continues...
1050+
/// }
1051+
///
1052+
/// ```
1053+
///
1054+
/// Usage:
1055+
///
1056+
/// ```shell
1057+
/// $ myapp generate-bash-completions > /etc/bash_completion.d/myapp
1058+
/// ```
1059+
pub fn gen_completions_to<W: Write, S: Into<String>>(&mut self, bin_name: S, for_shell: Shell, buf: &mut W) {
1060+
self.p.meta.bin_name = Some(bin_name.into());
1061+
self.p.gen_completions_to(for_shell, buf);
1062+
}
1063+
10261064
/// Starts the parsing process, upon a failed parse an error will be displayed to the user and
10271065
/// the process will exit with the appropriate error code. By default this method gets all user
10281066
/// provided arguments from [`env::args_os`] in order to allow for invalid UTF-8 code points,

src/app/parser.rs

+19-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ use app::meta::AppMeta;
2626
use args::MatchedArg;
2727
use shell::Shell;
2828
use completions::ComplGen;
29+
use std::fs::File;
30+
use std::path::PathBuf;
2931

3032
#[allow(missing_debug_implementations)]
3133
#[doc(hidden)]
@@ -99,10 +101,25 @@ impl<'a, 'b> Parser<'a, 'b>
99101
.nth(0);
100102
}
101103

102-
pub fn gen_completions(&mut self, for_shell: Shell, od: OsString) {
104+
pub fn gen_completions_to<W: Write>(&mut self, for_shell: Shell, buf: &mut W) {
105+
103106
self.propogate_help_version();
104107
self.build_bin_names();
105-
ComplGen::new(self, od).generate(for_shell)
108+
109+
ComplGen::new(self).generate(for_shell, buf)
110+
}
111+
112+
pub fn gen_completions(&mut self, for_shell: Shell, od: OsString) {
113+
use std::error::Error;
114+
115+
let out_dir = PathBuf::from(od);
116+
117+
let mut file = match File::create(out_dir.join(format!("{}_bash.sh", &*self.meta.bin_name.as_ref().unwrap()))) {
118+
Err(why) => panic!("couldn't create bash completion file: {}",
119+
why.description()),
120+
Ok(file) => file,
121+
};
122+
self.gen_completions_to(for_shell, &mut file)
106123
}
107124

108125
// actually adds the arguments

src/completions.rs

+7-20
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
use std::path::PathBuf;
2-
use std::fs::File;
3-
use std::ffi::OsString;
41
use std::io::Write;
52

63
use app::parser::Parser;
74
use shell::Shell;
85
use args::{ArgSettings, OptBuilder};
96

107
macro_rules! w {
11-
($_self:ident, $f:ident, $to_w:expr) => {
12-
match $f.write_all($to_w) {
8+
($buf:expr, $to_w:expr) => {
9+
match $buf.write_all($to_w) {
1310
Ok(..) => (),
1411
Err(..) => panic!(format!("Failed to write to file completions file")),
1512
}
@@ -18,33 +15,23 @@ macro_rules! w {
1815

1916
pub struct ComplGen<'a, 'b> where 'a: 'b {
2017
p: &'b Parser<'a, 'b>,
21-
out_dir: OsString,
2218
}
2319

2420
impl<'a, 'b> ComplGen<'a, 'b> {
25-
pub fn new(p: &'b Parser<'a, 'b>, od: OsString) -> Self {
21+
pub fn new(p: &'b Parser<'a, 'b>) -> Self {
2622
ComplGen {
2723
p: p,
28-
out_dir: od,
2924
}
3025
}
3126

32-
pub fn generate(&self, for_shell: Shell) {
27+
pub fn generate<W: Write>(&self, for_shell: Shell, buf: &mut W) {
3328
match for_shell {
34-
Shell::Bash => self.gen_bash(),
29+
Shell::Bash => self.gen_bash(buf),
3530
}
3631
}
3732

38-
fn gen_bash(&self) {
39-
use std::error::Error;
40-
let out_dir = PathBuf::from(&self.out_dir);
41-
42-
let mut file = match File::create(out_dir.join(format!("{}_bash.sh", &*self.p.meta.bin_name.as_ref().unwrap()))) {
43-
Err(why) => panic!("couldn't create bash completion file: {}",
44-
why.description()),
45-
Ok(file) => file,
46-
};
47-
w!(self, file, format!(
33+
fn gen_bash<W: Write>(&self, buf: &mut W) {
34+
w!(buf, format!(
4835
"_{name}() {{
4936
local i cur prev opts cmds
5037
COMPREPLY=()

tests/completions.rs

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
extern crate clap;
2+
3+
use clap::{App, Arg, SubCommand, Shell};
4+
5+
#[test]
6+
fn test_generation() {
7+
let mut app = App::new("myapp")
8+
.about("Tests completions")
9+
.arg(Arg::with_name("file")
10+
.help("some input file"))
11+
.subcommand(SubCommand::with_name("test")
12+
.about("tests things")
13+
.arg(Arg::with_name("case")
14+
.long("case")
15+
.takes_value(true)
16+
.help("the case to test")));
17+
let mut buf = vec![];
18+
app.gen_completions_to("myapp", Shell::Bash, &mut buf);
19+
let string = String::from_utf8(buf).unwrap();
20+
let first_line = string.lines().nth(0).unwrap();
21+
let last_line = string.lines().rev().nth(0).unwrap();
22+
23+
assert_eq!(first_line, "_myapp() {");
24+
assert_eq!(last_line, "complete -F _myapp myapp");
25+
}

0 commit comments

Comments
 (0)